I'm not sure how to ask this question correct since my understanding of the DOM is lacking.
What I'm trying to do is to catch any click event on any given DOM element. I then want to save the element type as well as the complete reference to element in a Database. But I'm not sure this is at all possible?
What i want to achieve is to save a hole interaction with a web app, in a way so you can later replay every action performed on the site, in a given session.
I have tried different approaches like getting the X and Y position of the clicked element, and later on trigger a click on those x-y coordinates, but theres several problems with this approach. I've also tried to traverse the Dom backwards until i reach the body tag, to build a unike selecter, but this also have it's shortcomings.. The best solution i can think of would be to save what ever $(this) contains.
If click events are the only thing you want to track, you probably want to add click event handlers to every clickable element on the page.
This would require starting at the <body> and walking the DOM, adding handlers as you go.
At the same time, I'd add a new data-xpath attribute to each element containing an XPath selector so you can use it in your handler to note the element being clicked, and so replay the user's interaction.
See http://www.w3schools.com/xpath/xpath_intro.asp for an introduction to XPath.
Faling a sleep yesterday i got an idea and ended up with this code today. - It works as intended but I'm guessing that Xpath would perform better!?
$(document).click(function(event) {
var target = $(event.target);
var parents = target.parents();
var myParents = '';
$($(parents).get().reverse()).each(function(key, value){
var parentIndex = $(this).index()+1;
myParents += $(this).prop("tagName")+':NTH-CHILD('+parentIndex+') > ';
});
var childIndex = target.index()+1;
var childTag = target.get(0).tagName;
myParents += childTag+':NTH-CHILD('+childIndex+')';
alert(myParents);
});
The above code will return a unique selector-string likes this:
HTML:NTH-CHILD(1) > BODY:NTH-CHILD(2) > SECTION:NTH-CHILD(1) > UL:NTH-CHILD(1) > LI:NTH-CHILD(3) > A:NTH-CHILD(1)
Related
I have a web application that, after clicking any node in the HTML, needs to retrieve the index of that node in its parent's childNodes array. However, I am having trouble getting the currently selected node through an onclick event. The returned target of the event is the containing element rather than the specific node inside the element. This difference is important when text nodes exist, such as:
<div>This is Node 1<span>node 2</span>, node 3, and <span>node 4</span></div>
If you click on the spans for Node 2 or Node 4, it's straightforward to know where you are. However, if you click on the text for Node 1 and Node 3, I can't seem to find where the event would help you figure out which part of the actual content was clicked on.
This happens to be important because a later operation needs to check for certain properties either forward or backward through the document until the first match. So, if both Node 2 and Node 4 are a match for the search, I need to know if I am in Node 1 or Node 3 in order to know which one to return. For example, if searching rightwards, starting in Node 1 means that Node 2 should be returned, and starting in Node 3 means that Node 4 should be returned. Obviously, this is a simplification, but it demonstrates the issue. Does anyone know the canonical solution for this? If I can get the node object or the index, that should be sufficient. jquery is fine, but not necessary.
Maybe somthing like this demo could help you out a bit:
document.getElementsByTagName('div')[0].addEventListener('click', function () {
var fullStr = this.innerHTML.replace(/<[^>]*>/g, ''),
sel = window.getSelection(),
str = sel.anchorNode.data,
clickPos = sel.focusOffset,
wordPosLeft = str.slice(0, clickPos + 1).search(/\S+$/),
wordPosRight = str.slice(clickPos).search(/\s/),
wordClicked,
nextWordRegex,
nextWordPosLeft,
nextWord;
if(wordPosRight < 0) {
wordClicked = str.slice(wordPosLeft);
} else {
wordClicked = str.slice(wordPosLeft, wordPosRight + clickPos);
}
nextWordRegex = new RegExp(wordClicked);
nextWordPosLeft = fullStr.search(nextWordRegex) + wordClicked.length;
nextWord = fullStr.slice(nextWordPosLeft).match(/^\s*(\S*)\s*.*$/)[1];
console.log('wordClicked: ' + wordClicked);
console.log('nextWord: ' + nextWord);
});
See this fiddle.
You need to get Your nodes in some containers. If You would click on "Node 1" text, function will return You a <div> element. But, if You would change Your code on this:
<div>
<span>This is Node 1</span>
<span>node 2</span>
<span>, node 3, and </span>
<span>node 4</span>
</div>
it would work and return <span> container. Not possible in other way, I think.
You can eventually make some JavaScript split() or regex operations.
If you're just trying to work out the text of the element you clicked, minus child nodes text, I have a solution:
$('body').on('click', function(e) {
alert('Node Text: '+$(e.target).clone().children().remove().end().text());
});
http://jsfiddle.net/xoegujqu/1/
Essentially, delegate the click event to the highest-level element you want this to run for (in this example it just used body, but you'll probably want to be more specific). use $(e.target) to get the element that was actually clicked, .clone() to clone it so you can modify it without affecting the actual page content, .children().remove() to remove all it's descendant elements, .end() to go back to the previous jQuery selector object, then finally .text() to get the remaining text content.
check out even bubbling / propagation
Also: https://developer.mozilla.org/en-US/docs/Web/API/EventTarget.addEventListener
useCapture section
It is not possible to do this as far as I know. You cannot:
Detect events on text nodes.
Detect the position of the text node relative to window or page.
This answer gives an idea with some good insight, but does not do what you want (return the index of the node).
I believe you are out of luck, unless you can find a way to use the solution above to determine index.
Okay so, I want to make an OnClick function in JavaScript that makes it so when a user clicks on it, it will change the word. Is there a replaceword() function or something that which will let me do so? I know this is not real code, but for example:
<p>Quickly <span onclick="replaceword('Surf');">Search</span> The Web!</p>
If there is, then can someone tell me also how to reverse the code maybe? So when they click on it the second time, it will change back to "Search"?
If you want to jump between multiple words, you'll need to store them someplace. You could have two words in the sentence, and toggle the visibility of one or the other (which doesn't scale well), or you could even store them as values on an attribute placed on the element itself.
<p>Hello, <span data-values="World,People,Stack Overflow">World</span>.</p>
I have placed all possible values within the data-values attribute. Each distinct value is separated from the other values by a comma. We'll use this for creating an array of values next:
// Leverage event-delegation via bubbling
document.addEventListener( "click", function toggleWords ( event ) {
// A few variables to help us track important values/references
var target = event.target, values = [], placed;
// If the clicked element has multiple values
if ( target.hasAttribute( "data-values" ) ) {
// Split those values out into an array
values = target.getAttribute( "data-values" ).split( "," );
// Find the location of its current value in the array
// IE9+ (Older versions supported by polyfill: http://goo.gl/uZslmo)
placed = values.indexOf( target.textContent );
// Set its text to be the next value in the array
target.textContent = values[ ++placed % values.length ];
}
});
The results:
The above listens for clicks on the document. There are numerous reasons why this is a good option:
You don't need to wait for the document to finish loading to run this code
This code will work for any elements added asynchronously later in the page life
Rather than setting up one handler for each element, we have one handler for all.
There are some caveats; you may run into a case where the click is prevented from propagating up past a particular parent element. In that case, you would want to add the eventListener closer to your target region, so the likeliness that bubbling will be prevented is less.
There are other benefits to this code as well:
Logic is separated from markup
Scale to any number of values without adjusting your JavaScript
A demo is available for your review online: http://jsfiddle.net/7N5K5/2/
No, there isn't any native function, but you can create on your own.
function replaceword(that, word, oword) {
that.textContent = that.textContent == oword ? word : oword;
}
You can call it like this:
<p>Quickly<span onclick="replaceword(this,'Surf','Search');">Search</span>The Web!</p>
Live Demo: http://jsfiddle.net/t6bvA/6
<p id="abc">Hello</p>
<input type="submit" name="Change" onclick="change()">
function change(){
var ab=document.getElementById('abc').value;
ab.innerHTML="Hi, Bye";
}
I think so this should help you, you should go to site such as w3schools.com, its basic and it will answer your doubt
You can try something like this if you wanna use jQuery
http://jsfiddle.net/R3Ume/2/
<script src="//code.jquery.com/jquery-1.10.2.js"></script>
<body>
<p>Hello <a id='name'>John<a></p>
<input id="clickMe" type="button" value="replace" onclick="onClick();" />
<script>
function onClick() {
$('#name').text('world');
}
</script>
I've got a modal window. What I want to happen is to remove certain elements from the page when the modal opens and add them back in right where they were after the modal closes. I don't want to do display:none, because that only hides them, I need them to actually be removed from the page. So I have a bit of jQuery to remove and add them back in after a timer just for testing...
UPDATED: With these additions to the code, it now grabs the element before, then adds it back in after that same element. The issue is, what if that element was also removed? Then it won't add back in! Also, won't javascript event handlers be lost in this? I'm developign a plugin, so it should interfere with the site as little as possibl,e but 3d elements have a bug in them with Safari that is impossible to get around.
Any ideas on how I could temporarily remove 3d elements without interfering with people's site too much?
$3delements = $('*').filter(function(){return $(this).css('-webkit-transform-style') == 'preserve-3d'});
$3delementsposition = $3delements.prev()
//On modal open
$3delements.remove();
//On modal close
$3delementsposition.after($3delements);
The problem is that this requires I specify a certain place in the DOM for them to come back in. I'd like the elements to come back in where they were. How can I make sure the elements don't change/move/lost information on the .remove to the .append.
Use .detach() and .append() to remove and reattach elements, it will maintain all your events and data.
If you add elements back in the reverse order that you removed them, they should all fall back in place
untested code
var elems3d = $(...);
var elemsRemoved = [];
// removing
elems3d.each(function(i,o) {
var elem = $(o);
elemsRemoved.push({
loc: elem.prev(),
obj: elem.detach()
});
});
// adding back
while (elemsRemoved.length) {
var elem = elemsRemoved.pop();
elem.loc.after(elem.obj);
}
Instead of removing the elements, replace them with placeholder elements (using replaceWith) then replace the placeholders with the original content when needed. Something like the following:
$3delements = $('*').filter(function(){return $(this).css('-webkit-transform-style') == 'preserve-3d'});
var originals = [];
$3delements.each(function() {
// Clone original, keeping event handlers and any children elements
originals.push($(this).clone(true));
// Create placeholder for original content
$(this).replaceWith('<div id="original_' + originals.length + '"></div>');
});
///
/// Do something asynchronous
///
// Replace placeholders with original content
for (var i in originals) {
$('#original_' + (i + 1)).replaceWith(originals[i]);
}
See clone and replaceWith in the jQuery docs for more info.
I have created the fiddle. Let me know if this fulfills your requirement.
http://jsfiddle.net/mNsfL/12/
I have an application in which the user needs to see the changes that have been made during the latest edit.
By changes I mean, the changes made in all inputs like a textarea, dropdowns.
I am trying to implement this by showing a background image on the right top and then when the user clicks this background image, a popup is shown which shows the difference.
I am using prototype 1.7.0.
My First question would be:-
1. What would be the best approach to implement this functionality?
2. Can I put a onClick on the background image?
There some functions in the jQuery library that I believe would be helpful to you. If you are using prototype, I would guess that there is some similar functionality you may utilize.
I would suggest writing some code like this:
var $input = $('input').add('textarea').add('select');
$input.each(function() {
var id = $(this).attr('id');
var value = $(this).val();
var hiddenId = 'hidden' + id;
var newHiddenInput = $("<input type='hidden'").val(value).attr('id',hiddenId);
$(this).after(newHiddenInput);
});
The above code will create a new hidden input for each input, textarea, and select on your page. It will have the same value as the input it duplicates. It will have an id equivalent to prepending the id with the word 'hidden'.
I don't know if you can attach a click handler to a background image. If your inputs are enclosed inside a <div>, you may be able to get the result you want by attaching the click handler to your div.
In any case, you should now have the old values where you can easily compare them to the user's input so that you can prepare a summary of the difference.
Prototype gives us the Hash class which is almost perfect for this but lacks a way of calculating the difference with another hash, so let's add that...
Hash.prototype.difference = function(hash)
{
var result = this.clone();
hash.each(function(pair) {
if (result.get(pair.key) === undefined)
// exists in hash but not in this
result.set(pair.key, pair.value);
else if (result.get(pair.key) == pair.value)
// no difference so remove from result
result.unset(pair.key);
// else exists in this but not in hash
});
return result;
};
This is no way to tell if an element was clicked on just it's background image - you can find out the coordinates where it was clicked but that is not foolproof, especially since CSS3 adds complications like multiple backgrounds and transitions. It is better to have an absolutely positioned element to act as a button.
$('button-element').observe('click', function() {
var form_values = $H($('form-id').serialize(true));
if (old_values) {
var differences = old_values.difference(form_values);
if (differences.size()) {
showDiffPopup(differences);
}
}
window.old_values = form_values;
});
// preset current values in advance
window.old_values = $H($('form-id').serialize(true));
All that remains is to implement showDiffPopup to show the calculated differences.
For example I have long list of buttons: <input type=button name=clickbutton onclick='dosomething(this)'>
But instead of putting call to the same function in every button it would be more rational to add a single listening event that would wait for click of that button with this name. But again, there are many this buttons so I need pass clicked object to a function. For example this function must update a textarea field that is adjacent (nextSibling) to this button.
You could use jQuery:
$('input[name|="clickbutton"]').click(function() {
var currentButton = $(this);
alert(currentButton.attr('id'));
});
This attaches a click function to all input elements with a name of 'clickbutton'. currentButton is the input element that was just clicked. The function then alerts the id of the currentButton.
UPDATE: I've created a working example for you here: http://jsfiddle.net/Damien_at_SF/cdsWk/1/
Hope that helps :)
Look up event delegation. This may be what you need. There are many good resources for it on google.
Update:
Sorry, I could have put a little more effort into the answer, Here are some results Google Search...
You would then use a switch to determine what action you want to take based on the event.name attribute. Cool thing about that approach is if you have a great deal of elements on the page with events, it sounds like you do, it should make your page feel more responsive; as the browser does not have to deal with the extra overhead of on event per element.
var buttons = document.getElementsByName('clickbutton');
for (var i = 0; i < buttons.length; i++)
buttons[i].onclick = function(){
this.nextSibling.innerHTML = 'clicked!';
}