So say I have some code that creates an indefinite number of comments in a main section of the page, such as
function createcomments(comments) {
var main = document.getElementById("main");
for (var i = 0; i < comments.length; i++) {
var comment = document.createElement("quoteblock");
comment.innerHTML = comments[i];
main.appendChild(comment);
comment.classList.add("comment");
}
}
And every time a visitor to my page hovered over a comment the background would turn red or something:
window.onload = function() {
var comments = document.querySelectorAll(".comment");
// code for handling .onmouseover and .onmouseout
// for each element in the array
}
How would I do that? I think there is a way to do it with jQuery, but I was wondering if there's a way to do it with JavaScript.
In jQuery there are this two helper functions delegate() and live().
They work as nicely described in this blog post.
Actually you can attach an eventHandler to a parent element that is than listeing to all mouse events (and other events). Using delegation you then check on the parent elements eventHandler if the event is coming from a specific child.
<ul>
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
here we add some simple html, but we dont attach to the span element for a click handler, but to the div. in the div eventHandler we then check, what target actually got clicked
var divs = document.querySelector("ul");
divs.addEventListener("click", function(ev) {
if (ev.target.tagName === "LI")
console.log("A list element (" + ev.target.innerText + ") was clicked");
});
The whole reason behind this delegation is performance. also, if you remove or add items dynamically, the event handling works as expected without any additional work.
If you dont want to use the whole jQuery for this simple step, I still suggest you use some framework, as it is always better to use community support than reinventing the wheel
try http://craig.is/riding/gators, looks nice :)
Related
I'm very new to javascript and I'm trying to give some dynamic features to a site I'm working on. In particular, I want to have an unfolding menu item that unfolds and folds back whenever the mouse is on and off of it, respectively.
I got the unfolding part down but the event listener that triggers the folding back does it whenever the mouse if off of the area where the menu item used to be, event though it's just been extended.
The HTML markup looks like this:
<nav id="nav">
<ul>
<li id="elemPlaces"><ul id="drop"><li>Places</li></ul></li>
</ul>
</nav>
The event listeners are declared like this:
<script type="text/javascript">
var extended = false;
var listPlace = (<?php echo json_encode($list_place); ?>);
document.getElementById("elemPlaces").addEventListener("mouseover", extend);
document.getElementById("elemPlaces").addEventListener("mouseout", retract);
</script>
and the extend and retract functions are the following:
function extend()
{
if(!extended)
{
var drop = document.getElementById("drop");
var form = document.createElement("form");
form.setAttribute("action", "place.php");
form.setAttribute("method", "get");
drop.appendChild(form);
for(var i = 0; i < listPlace.length; i++)
{
var li = document.createElement("li");
var input = document.createElement("input");
li.setAttribute("class", "dropOption");
input.setAttribute("type", "submit");
input.setAttribute("name", "location");
input.setAttribute("value", listPlace[i]);
li.appendChild(input);
form.appendChild(li);
}
extended = true;
}
}
function retract()
{
var dropOption = document.getElementsByClassName("dropOption");
while(dropOption[0])
{
dropOption[0].parentNode.removeChild(dropOption[0]);
}
extended = false;
}
I realize it all must look amateurish but like I said, I'm new to this. I'd really appreciate it if someone could help me.
Okay JSFiddle seems to be down, so here is a Plunker
Basically :
Use mouseenter and mouseleave (see this question)
I believe in your case you just want to redirect to a page passing a GET option. You can do it using <a href="yourpage.php?yourOption=yourValue">. It is simpler. You don't need forms for this.
As I said, here it's a bad idea to modify the dom. Or well, it depends on where your list comes from. If your $list_place comes from an AJAX asynchronous request, then obviously you can't do anything else but modify the DOM dynamically. However if you know beforehand what the content of your list will be, best it to just write everything to the HTML, and add classes like class="unexpanded/expanded", and have a CSS .unexpanded{display: none}. Then you just need to toggle/change the class
So basically what you want to so, is make sure the menu stays unfolded a certain amount of time before folding back ? Here are some ideas
Instead of calling retract on mouseout, you could call another function with a timeout, like retract_after
function retract_after(){
setTimeout(function(){ retract() }, 3000);
}
But this could lead to weird situations (imagine the user moves the mouse on the menu just after moving it out, before the timeout expired...). So you might want to lookup how to empty the queue or remove EventListeners.
Or, what you could do, is only attach the "retract" eventListener at the end of the mouseover function (and also eventually with a timeout), and remove the eventlistener while or after it is retracting.
Also the animate() function of jquery library already somewhat produces by default the behavior you're looking (it queues animation, but if a user quickly triggers mouseenter and mouseout event listeners, the menu will keep folding/unfolding till it empties the queue)
This question already has an answer here:
How to add data dynamically with jQuery?
(1 answer)
Closed 8 years ago.
Can you please tell me how to add data dynamically? I am not able to make same as static.
http://jsfiddle.net/eHded/1537/
Please check this fiddle and open panel it show menu as well as submenu options.
Now I need to make same on button click. I have add button. I want add menu option (which is submenu of Additem in panel) can we do this.
I get the button click event, but that is not working fine.
http://jsfiddle.net/eHded/1538/
$('#add').click(function(){
// alert('e')
$('#tree li').append('<ul><li> <span>jjjj</span><ul>')
var tree = $('#tree').goodtree({'setFocus': $('.focus')});
})
Try this instead:
$('#add').click(function(){
var data = $('#tree li').html();
$('#tree li').html(data+'<ul><li> <span>jjjj</span><ul>')
var tree = $('#tree').goodtree({'setFocus': $('.focus')});
});
JsFiddle
You did not close your <ul> and <li> tag.
Also, you probably don't want to add the new element to every <li> in the tree.
Try the following instead (http://jsfiddle.net/eHded/1541/):
$(function() {
var tree = $('#tree').goodtree({
'setFocus': $('.focus')
});
$('.slider-arrow').click(function() {
var anchor = this;
var removeClass = "show";
var addClass = "hide";
var diff = "+=300";
var arrows = "«";
if ($(anchor).hasClass("hide")) {
diff = "-=300";
removeClass = "hide";
addClass = "show";
arrows = "»";
}
$(".slider-arrow, .panel").animate({
left: diff
}, 700, function() {
// Animation complete.
$(anchor).html(arrows).removeClass(removeClass).addClass(addClass);
});
});
$('#add').click(function() {
$('#tree').children().last().append('<ul><li><span>jjjj</span></li></ul>');
$('.goodtree_toggle').off();
$('#tree').goodtree({
'setFocus': $('.focus')
});
})
});
There also seems to be a problem with the goodtree plugin you are using.
The items only expand every second time you click them.
Ideally the plugin would use event delegation to make sure that the expanding still works even if you change the tree, without having to re-initialize. Since you re-initialize the tree after every insertion, there are multiple event handlers attached to the nodes. So when you have an odd number of nodes inserted, it will toggle the element an even times (since you initialized it in the very beginning when it was empty), giving you no result.
You can fix it by removing all event handlers on the toggle before you re-initialize:
$('.goodtree_toggle').off();
I would really look for a better plugin instead.
I have 2 buttons, A and B. On clicking A the hashtag changes and I want to call the window.onhashchange function. But, on clicking B, I am manually appending a hashtag to the URL. In the later case, I don't want window.onhashchange to be triggered.
From your question I'm assuming you have generated HTML by PHP something along this:
<ul id='list-of-links'>
<li id='A'>Link A</li>
<li id='B'>Link B</li>
</ul>
Having that you wish that A does something else than B. In your JavaScript you can use the hash function.
var targetHash = window.location.hash.slice(1);
if( targetHash != 'posB' ) {
//do something as it's not 'posB'
};
Hope that helps.
Without any code all I can suggest is to check the id of the button that causes the event and act accordingly.
With little help of this answer and this one you can temporary remove event handles. Here's working example of jsfiddle.net with click event so assuming jQuery >= 1.8 it'll be similar, thus something like this:
// Initialization, add some events
$.hashchange(...);
// Store event handles for hashchange
hashchange_handles = [];
$.each( $._data($('#foo').get(0), "events")['hashchange'], function(i,handle){
// alert(handle.handler);
hashchange_handles.push(handle.handler);
});
// Remove handles
$.off('hashchange');
// Do your stuff here
// Restore event handler
length = hashchange_handles.length;
for(var i=0; i<length; i++) {
$.hashchange( hashchange_handles[i]);
}
It worked for click so hopefully it'll work for hashchange.
Note: I know the code is not perfect, you should tuned it. Plus I have tested it only for click.
I've been struggling with what seems to be a simple problem for a few hours now. I've written a REGEX expression that works however I was hoping for a more elegant approach for dealing with the HTML. The string would be passed in to the function, rather than dealing with the content directly in the page. After looking at many examples I feel like I must be doing something wrong. I'm attempting to take a string and clean it of client Events before saving it to our Database, I thought jQuery would be perfect for this.
I Want:
Some random text click here and a link with any event type
//to become:
Some random text click here and a link with any event type
Here's my code
function RemoveEvilScripts(){
var myDiv = $('<div>').html('testing this Do it! out');
//remove all the different types of events
$(myDiv).find('a').unbind();
return $(myDiv).html();
}
My results are, the onClick remains in the anchor tag.
Here's a pure Javascript solution that removes any attribute from any DOM element (and its children) that starts with "on":
function cleanHandlers(el) {
// only do DOM elements
if (!('tagName' in el)) return;
// attributes is a live node map, so don't increment
// the counter when removing the current node
var a = el.attributes;
for (var i = 0; i < a.length; ) {
if (a[i].name.match(/^on/i)) {
el.removeAttribute(a[i].name);
} else {
++i;
}
}
// recursively test the children
var child = el.firstChild;
while (child) {
cleanHandlers(child);
child = child.nextSibling;
}
}
cleanHandlers(document.body);
working demo at http://jsfiddle.net/alnitak/dqV5k/
unbind() doesn't work because you are using inline onclick event handler. If you were binding your click event using jquery/javascript the you can unbind the event using unbind(). To remove any inline events you can just use removeAttr('onclick')
$('a').click(function(){ //<-- bound using script
alert('clicked');
$('a').unbind(); //<-- will unbind all events that aren't inline on all anchors once one link is clicked
});
http://jsfiddle.net/LZgjF/1/
I ended up with this solution, which removes all events on any item.
function RemoveEvilScripts(){
var myDiv = $('<div>').html('testing this Do it! out');
//remove all the different types of events
$(myDiv)
.find('*')
.removeAttr('onload')
.removeAttr('onunload')
.removeAttr('onblur')
.removeAttr('onchange')
.removeAttr('onfocus')
.removeAttr('onreset')
.removeAttr('onselect')
.removeAttr('onsubmit')
.removeAttr('onabort')
.removeAttr('onkeydown')
.removeAttr('onkeypress')
.removeAttr('onkeyup')
.removeAttr('onclick')
.removeAttr('ondblclick')
.removeAttr('onmousedown')
.removeAttr('onmousemove')
.removeAttr('onmouseout')
.removeAttr('onmouseover')
.removeAttr('onmouseup');
return $(myDiv).html();
}
I am adding a custom data attribute data-js-href to various HTML elements, and these elements should behave just like a link when clicked. If a link within such an element is clicked, the link should take precedence and the data-js-href functionality should be ignored, though. Furthermore, the solution also needs to work with elements that are dynamically added at a later time.
So far, I have come up with the following solution. It basically checks if the click was performed on a link, or any child element of a link (think <a href='…'><img src='…' alt='…' /></a>).
// Make all elements with a `data-js-href` attribute clickable
$$('body').addEvent('click:relay([data-js-href])',
function(event, clicked) {
var link = clicked.get('data-js-href');
if (link && !event.target.match('a')) {
var parents = event.target.getParents();
for (var i = 0; i < parents.length && parents[i] != clicked; i++) {
if (parents[i].match('a')) {
return;
}
}
document.location.href = link;
}
});
It works, but it feels very clumsy, and I think that there has to be a more elegant solution. I tried something along the lines of
$$('body').addEvent('click:relay([data-js-href] a)',
function(event, clicked) {
event.stopPropagation();
}
but to no avail. (I littered the code with some console.log() messages to verify the behavior.) Any idea is welcome.
you can do this with 2 delegated events - no reverse lookups and it's cheap as they will share the same event. the downside is, it is the same event so it will fire for both and there's no stopping it via the event methods (already bubbled, it's a single event that stacks up multiple pseudo event callbacks and executes them in order--the event has stopped but the callbacks continue) That's perhaps an inconsistency in mootools event vs delegation implementation but it's a subject of another issue.
Workarounds for now can be:
to have the 2 event handlers communicate through each other. It will scale and work with any new els added.
to add the delegators on 2 different elements. eg. document.body and #mainWrap.
http://jsfiddle.net/dimitar/J59PD/4/
var showURL = function(howLong) {
// debug.
return function() {
console.log(window.location.href);
}.delay(howLong || 1000);
};
document.id(document.body).addEvents({
"click:relay([data-js-href] a))": function(e) {
// performance on lookup for repeat clicks.
var parent = this.retrieve("parent");
if (!parent) {
parent = this.getParent("[data-js-href]");
this.store("parent", parent);
}
// communicate it's a dummy event to parent delegator.
parent.store("linkEvent", e);
// let it bubble...
},
"click:relay([data-js-href])": function(e) {
// show where we have gone.
showURL(1500);
if (this.retrieve("linkEvent")) {
this.eliminate("linkEvent");
return;
}
var prop = this.get("data-js-href");
if (prop)
window.location.href = prop;
}
});
Discussed this with Ibolmo and Keeto from the mootools team on IRC as well when my initial attempt failed to work and both callbacks fired despite the event.stop: http://jsfiddle.net/dimitar/J59PD/
As a result, there was briefly a ticket open on the mootools github issues: https://github.com/mootools/mootools-core/issues/2105 but it then went into a discussion of what the right thing to do from the library standpoint is and how viable it is to pursue changing the way things work so...