I am new to Javascript and trying to create a Javascript click function that will display the input element when the button is clicked and hide it when the button is clicked again. My console shows no error messages but when I click the button the input element does not hide. Again I am very new to Javascript, I appreciate any feedback!
document.addEventListener("DOMContentLoaded", load);
function load() {
let button = document.querySelector("button");
button.addEventListener("click", clickMe);
}
// Click Function to change the display value of the input
function clickMe() {
let input = document.getElementById("popup");
if (input.style.display == "none") {
input.style.display = "block";
} else {
input.style.display = "none";
}
}
<!-- <form> I commented out the form element because it does not work to use .addEventListener inside form -->
<label for="button"></label>
<fieldset>
<ol>
<li><button type="button" onclick="clickMe()">Click me!</button></li>
<li><input type="text" name="popup" id="popup" placeholder="placeholder"></li>
</ol>
</fieldset>
<!-- </form> -->
You are adding the click listener twice, in HTML with onclick and in JavaScript with addEventListener(). Therefore it is executed twice for each click.
Since your listener toggles between two states, calling it twice behaves the same as calling it none at all. This is the issue you are observing.
Adding the listener only once solves this issue. Prefer to keep addEventListener(); reasons are stated later on.
Since you mentioned that you would "appreciate any feedback", I collected a few points that may be of interest:
Good parts
JavaScript
Deferring code execution with "DOMContentLoaded"
In StackOverflow snippets, the script is always added as <body>'s last element, however in other scenarios this can not be ensured.
Scripts can be added in many different ways to pages:
Inline scripts:
Regular scripts.
type="module" scripts.
External scripts:
With defer.
With async.
Regular scripts.
type="module" scripts.
Additionally, for regular non-deferred scripts, position of the script element in the document is also relevant.
But by using "DOMContentLoaded", you ensure that all relevant elements have been loaded regardless of how the script is added to the page, which is great!
Here it is also more preferable to use "DOMContentLoaded" over "load", since we only want to wait until the DOM has loaded; the loading of resources is irrelevant for our implementation.
Use of getElementById()
In this case, using getElementById() is more expressive than using querySelector() with an ID selector, because it conveys our intent more clearly: Getting an element by its ID. The approach of using querySelector() with an ID selector would be less clear at a glance.
Note that you can practically add IDs to every element, but reserving them for when it makes sense keeps your code complexity maintainable.
However, if your document structure allows for (subjectively) simple selectors to uniquely select elements, it may be preferable to use querySelector() instead of adding an ID solely to use getElementById().
Use of modern declarator: let
The declarator var has been de facto deprecated in favour of let/const for various reasons, so usually the new declarators are preferred.
While let may be used in all places where const is used, its usage conveys a different intent:
const is used for a constant reference, whereas the reference of let may change during runtime.
Sidenote: Referenced values may still be mutated, including values referenced by const. Primitive values are immutable, but e.g. objects are mutable.
Regardless, learning JavaScript with let/const is usually easier and more preferable than learning it with var.
Comments!
Useful comments are always appreciated. Since your function name is not expressive, your comment provides the missing description.
However, if your function name would be expressive enough (see section "Naming conventions" below), the comment would be obsolete.
HTML
Use of type="button"
By default, buttons have type="submit", which submits the form on click. But you want to use your button as an actual button.
This could be realized by calling event.preventDefault() in its listener, but using type="button" instead makes for semantic and more expressive HTML code and keeps your JS code simpler.
Unfortunately, it ambiguates whether the button-click would submit the form when only inspecting the JS code, but this is a trade-off for keeping the JS code simple(r).
Use of uncommon elements
Some elements (such as <fieldset>) are more rare on typical (commercial?) pages but more common on document-like pages. I personally enjoy seeing these uncommon elements being used.
Note that <fieldset> is usually used together with <legend> to provide a descriptive heading for it.
Labelling
Your attempt of providing a label for the button is great!
A label's for attribute is used to reference another element by its ID. Unfortunately, your button doesn't have an ID, nor does the referenced ID "button" exist.
Note that a button's content may act as that button's label, so an additional label may actually not be necessary.
Your placeholder attribute currently only holds placeholder text, presumably because this is just a StackOverflow snippet. But providing instructions is great and improves the user experience.
Feedback
TL;DR
Prefer addEventListener() over onevent attributes/properties.
Prefer reverting styles over overriding with assumed values.
Follow a naming convention, your own or an established one. This keeps names meaningful.
Prefer browser-provided features (checkbox) over custom implementations (custom toggle button).
Keep accessibility in mind.
Separation of concerns
Nowadays web technologies (HTML, CSS and JS) allow for Separation of concerns: You can separate structural code (HTML), presentational code (CSS) and functional code (JS). Separating your code into these "concerns" allows for easier maintenance in the future.
That said, you are adding functionality to your page in two ways:
In HTML with onclick.
In JS with addEventListener().
As mentioned before, functionality is more related to JS than HTML, so we should prefer addEventListener() to keep our concerns organized.
Sidenote: Adding the listener twice actually cancels its effect since it would be called twice per interaction, toggling back and forth between two states. Adding only one listener conveniently fixes this bug.
Using addEventListener() is also more preferred than assigning to the onclick property (or similar), because addEventListener() ...
Allows adding multiple listeners.
Allows more configurations for the listener.
Exists on many different objects, e.g. elements, window, document, etc.
Inline styles
The HTMLElement.style property reflects that element's inline style declarations. You hide the element by setting style.display = "none", which is fine. But to unhide you assume that the previous value was block.
If the element's display value was not block, then your toggling behaviour may appear buggy. To fix this we should remove our assumption and just revert to the actual previous value.
We can revert to the previous value in multiple ways, but the easiest is to just remove our inline declaration:
const [revertingButton, overridingButton] = document.querySelectorAll("button");
revertingButton.addEventListener("click", evt => {
const button = evt.target.closest("button");
const div = button.nextElementSibling;
if (div.style.display === "none") {
// Remove inline declaration
div.style.display = "";
} else {
div.style.display = "none";
}
});
overridingButton.addEventListener("click", evt => {
const button = evt.target.closest("button");
const div = button.nextElementSibling;
if (div.style.display === "none") {
// Override style declaration with assumption
div.style.display = "block";
} else {
div.style.display = "none";
}
});
div {display: flex} /* Notice this declaration! */
/* Ignore; for presentational purposes */
body {font-family: sans-serif}
div::before, div::after {
content: "";
width: 50px;
height: 50px;
display: block;
}
div::before {background-color: lightpink}
div::after {background-color: lightblue}
section {margin-block: .8rem}
header {
font-size: large;
font-weight: bold;
}
<section>
<header>Removing inline declaration</header>
<button>Toggle visibility</button>
<div></div>
</section>
<section>
<header>Assuming previous value</header>
<button>Toggle visibility</button>
<div></div>
</section>
Naming conventions
Some of your names are either confusing or not meaningful (e.g. function name clickMe).
This can be fixed by following a naming convention. You can either decide on one by yourself, or choose (from) established naming conventions. Any naming convention is better than none. Here are some naming conventions:
Google's style guide: Simple and loose convention.
No abbreviations.
Meaningful over short names.
MDN's style guide: Stricter but still quite loose convention.
No abbreviations.
Short names.
No articles or possessives.
No type information in names (no "list", "array", "map") and no Hungarian Notation.
By following the point "meaningful names" we may implement these changes:
Change function name clickMe to toggleInputVisibility.
Change function name load to loadHandler, or use an anonymous function since the surrounding code (the context) provides enough meaning.
Accessibility
It is good to practice implementing behaviours such as toggle buttons yourself, but for conciseness, uniformity and accessibility you should prefer the browser-provided features in any real-world projects. That said, let's focus on the accessibility aspect:
Controls (e.g. your toggling button) should indicate their state and what element they control. Referencing the controlled element is done with aria-controls.
Indicating the control's state can in our case be done with aria-checked since our button is a 2-state control, or by using an <input type="checkbox"> which natively indicates its own state.
Apart from indicating their state, checkboxes also indicate the correct role: Our button is of role="button" by default, but we effectively use it as a checkbox. Therefore this button's role should be checkbox.
Labels
Controls should also provide meaningful descriptions regarding their actions: The text "Click me!" is not descriptive. Instead, "Toggle input visiblity" (or just "Toggle visibility") would be better. If we used a checkbox instead of a button, its description should be provided in a label that references the checkbox with its for attribute.
Labels come with the additional benefit of increasing the interactable region for their referenced input element. For example, clicking a textfield's label focuses the textfield, or clicking a checkbox's label toggles the checkbox.
Also important to note is that "placeholder text is not a replacement for labels". When using input elements, always provide a label:
With aria-labelledby, or
With aria-label, or
With <label>s and the for attribute.
Nesting an input element inside a label element is technically enough according to the HTML specification, but "generally, explicit labels are better supported".
Examples
Here is an accessible example of using a button as a visibility toggle:
const button = document.querySelector("button");
button.addEventListener("click", () => {
// ariaChecked is a string, but we want to invert its boolean equivalent
button.ariaChecked = !(button.ariaChecked === "true");
const input = document.getElementById(button.getAttribute("aria-controls"));
input.style.display = button.ariaChecked === "true" ? "" : "none";
});
/* Add visual indicator */
button::before {display: inline}
button[aria-checked=true]::before { content: "\2705 "}
button[aria-checked=false]::before { content: "\274E "}
<div>
<button role="checkbox" aria-checked="true" aria-controls="button-input">Toggle visibility</button>
<input id="button-input">
</div>
And here is an equivalent example but using a checkbox:
const checkbox = document.querySelector("input[type=checkbox]"); // or #checkbox
// Prefer to use "change" over "click" for semantics, but both work
checkbox.addEventListener("change", () => {
// Toggling checkedness is default behaviour
const input = document.getElementById(checkbox.getAttribute("aria-controls"));
input.style.display = checkbox.checked ? "" : "none";
});
<div>
<label for="checkbox">
<input id="checkbox" type="checkbox" checked aria-controls="checkbox-input">Toggle visibility
</label>
<input id="checkbox-input">
</div>
Things to notice:
In the button-example we repurpose an element to behave like another. This makes the code less readable and more confusing. The HTML in the checkbox-example is therefore more understandable.
Toggling the checkedness comes built-in in the checkbox-example.
Using the checkedness of a checkbox is easier than using the checkedness of an ARIA-enhanced element, because .checked is boolean whereas .ariaChecked is a string.
Semantically, we want to react to a state change (event type change), not to the cause of a state change (event type click) which may have been done manually.
The input fields in both examples are generic for simplicity's sake, therefore they do not have an associated label. As mentioned before, input elements should always be associated with a label in real-world scenarios.
Two things:
You don't need to add both the onclick attribute and an event listener. Just use .addEventListener, it is the preferred way.
Don't use tag selectors unless you really want to affect every <button> element. In your case, you should use an ID, such as "clickMeBtn".
document.addEventListener("DOMContentLoaded", load);
function load() {
let button = document.querySelector("#clickMeBtn");
button.addEventListener("click", clickMe);
}
// Click Function to change the display value of the input
function clickMe() {
let popup = document.getElementById("popup");
if (popup.style.display == "none") {
popup.style.display = "block";
} else {
popup.style.display = "none";
}
}
<form>
<label for="button"></label>
<fieldset>
<ol>
<li><button type="button" id="clickMeBtn">Click me!</button></li>
<li><input type="text" id="popup" name="popup" placeholder="placeholder"></li>
</ol>
</fieldset>
</form>
JS will execute code from top to bottom, in your code, you define function ClickMe() after your function load() so it can not call.
Just move it on.
There also other solution, that you dont need to add addEventListener with event click because in button tag already have default event click, just remove that event can solve this problem
Best way to achieve this is to create a CSS class to hide and unhide the element.
document.querySelector("button").addEventListener("click", function (evt) {
document.querySelector("input").classList.toggle("hidden");
});
.hidden{
display:none
}
<button>click</button>
<input type=text></input>
Related
I am trying to write a greasemonkey script which adds an onClick listener on a button (define below) and do some specific things. I was trying to get this button obj using document.getElementsByTagName but it returned null. I tried to iterate over document.anchors and it returned null as well. how can i get this element object and add onclick event to it.
<a class="editicon" aria-label="Edit this field" role="button" href="#"><img src="https://url-to/images/edit.png?3423434"></a>
There is already an onclick added to this object, I don't want to replace it.
UPDATE
Adding my Greasemonkey script
// ==UserScript==
// #name cr
// #namespace my_namespace
// #version 1
// #grant none
// ==/UserScript==
(function(){
console.log('test ...')
var editicon = document.getElementsByTagName('editicon');
console.log(editicon);
})();
First, your question talks about a button, but your code does not include one. Instead of using an <a> element and then disabling its native navigation function with href="#", it would be semantically better to use an actual <button> element.
Second, you should not use inline HTML event attributes (onclick, onmouseover, etc.) as they:
Create "spaghetti code" that doesn't scale, is hard to read, leads to duplication and doesn't follow the "separation of concerns" methodology.
Create global anonymous wrapper functions around your event attribute value that alter the this binding of your code.
Don't follow the W3C Event Standard of using the addEventListener() API.
Now, there are several ways to get a reference to a DOM element and some are better than others depending on your HTML structure and how many elements you are trying to get. document.getElementsByTagName() returns a "node list" of all the elements that were found. Even if no elements were found, you still get this list as the returned object (the list will just be empty when no elements were found). To extract a particular element from the result, you'll need to pass an index into that list, which is an "array-like" object. Additionally, getElementsByTagName returns a "live" node list, meaning that it will re-scan the document upon every interaction to ensure that the most current list is provided. While this can be beneficial in some circumstances, it comes with a performance cost.
This, for example, would extract the first a element in the document:
var myElement = document.getElementsByTagName("a")[0];
But, since you are only expecting a single element, that is overkill. To get just one element, and if that element has an id attribute on it, you can/should use: document.getElementById("theIdHere"); as getElementById() is generally the fastest way to find a single element in your HTML structure.
Additionally, there are other ways to get an element or elements, like querySelector() and querySelectorAll(), which allow you to use CSS selector syntax in your JavaScript queries.
For your code, see the following snippet:
// Get a reference to the first <button> element in the document
var b = document.querySelector("button");
// Or, if the element has an id, the best solution would be:
var b = document.getElementById("btn");
// Add a click event handler to the element
b.addEventListener("click", handleClick);
// Function that will be called when anchor is clicked
function handleClick(){
alert("You clicked me!");
}
button { background-color:rgba(0,0,0,0); }
img { width: 50px; }
<button class="editicon" aria-label="Edit this field" role="button" id="btn"><img src="https://s-media-cache-ak0.pinimg.com/736x/5a/42/c6/5a42c6224e3ce7fa9837965270bfcdd9--smiley-face-images-smiley-faces.jpg"></button>
I want to implement a simple menu just by using Vanilla JS. So I have a working onclick function, where I just twist visibility property on click of the menu item. CSS is by default set on visibility:hidden
function getContentP() {
var div = document.getElementById("menu1Content");
if (div.style.visibility === "hidden") {
div.style.visibility = "visible";
} else {
div.style.visibility = "hidden";
}
};
<a href="#menu1" onclick="getContentP()">
<h2>title</h2>
</a>
<!-- CONTENT BOX, show on clicks-->
<div id="menu1Content" style="background: #fefefe">
Some content to make it visible
</div>
This works as expected, but really slowly, and with poor results.
Any good suggestion to improve the code? And maybe add some nice transitions like fadeIn effect without using jQuery?
Thanks a lot.
Im not sure what slowly means in this case or what poor results you are seeing, but in general the thing that jumps out at me is the usage of onclick. When passing code like that to a dom element the runtime will essentially eval the snippet which is bad for a number of reasons, and certainly not great for performance (also likely can never be optimized by the vm).
element.addEventListener('click', function() { 'your stuff here' }, false);
may give you better performance but id be shocked if you can even tell the difference unless this is called thousands or maybe millions of times.
You could also cache the dom element since you seem be doing a lookup by id, then you dont have to do a potentially expensive dom search every time the thing is clicked.
I'm not css guru but you can probably get something cool with this without too much effort.
[from comments] I usually need two clicks on the link to get it to show in the first place, which is really strange
No, that is anything but strange.
element.style.property only allows you to access CSS values that where set either directly via a style attribute on the element, or by assigning a value to element.style.property via script.
Both is not the case here, and your class="hidden" that you had in your code initially (before editing it out) was likely to set the element’s visibility hidden from the stylesheet, right?
Therefor, when you check the current visibility of the element here for the first time,
if (div.style.visibility === "hidden") {
that condition is not true, so you set the visibility to hidden in your else-branch – and therefor the element stays hidden.
Now, on your second click, the value hidden was set via script, so now you can read it back. Now that if condition results in true, and your script sets the element to visible.
Easy way to work around this – turn the logic around:
Check div.style.visibility === "visible", and set the element to hidden if that’s true, and visible if it is false.
The element is hidden (via the stylesheet) when your page first loads. Because of what I explained before, div.style.visibility can’t read the value on the first click, so the condition will be false, and the element will be made visible.
The HTML (note the added id):
<a href="#" id="menu1Toggle" style="visibility: hidden;">
<h2>title</h2>
</a>
The handler (note the added ev and preventDefault()):
function getContentP(ev)
{
var div = document.getElementById("menu1Content");
div.style.visibility = div.style.visibility === "hidden" ? "visible" : "hidden";
ev.preventDefault();
};
Attach the event with:
document.getElementById("menu1Toggle").onclick = getContentP;
You could use opacity if you want to fadeIn/Out. Just add a CSS transition. An easy toggle method would be:
elem.style.opacity = +!parseInt(elem.style.opacity);
Not sure if this would perform better/worse, but here is a Fiddle
so i implemented a bit of jQuery that basically toggles content via a slider that was activated by an <a> tag. now thinking about it id rather have the DIV thats holding the link be the link its self.
the jQuery that i am using is sitting in my head looks like this:
<script type="text/javascript">
function slideonlyone(thechosenone) {
$('.systems_detail').each(function(index) {
if ($(this).attr("id") == thechosenone) {
$(this).slideDown(200);
}
else {
$(this).slideUp(600);
}
});
}
</script>
i was using this as a index type box so there are several products when you click on the <a> tag that used to be an image* it would render a bit of content beneath it describing the products details:
<div class="system_box">
<h2>BEE Scorecard database</h2>
<p>________________</p>
</div>
the products details are wrapped in this div.
<div class="systems_detail" id="sms_box">
</div>
so when you click on what used to be a image* it would run the slideonlyone('div_id_name') function. the function above then first closes all the other divs with the class name 'system details' and then opens/slides the div with the id that was passed into the slideonlyone function. that way you can toggle products details and not have them all showing at once.
note i only kept the <a> tag to show you what was in there i will be getting rid of it.
note: i had an idea of just wrapping the whole div in an <a> tag but is that good practice?
So now what i am wondering is since you need JavaScript to run onclick on a div tag how do you write it so that it still runs my slideonlyone function?
Using obtrusive JavaScript (i.e. inline code) as in your example, you can attach the click event handler to the div element with the onclick attribute like so:
<div id="some-id" class="some-class" onclick="slideonlyone('sms_box');">
...
</div>
However, the best practice is unobtrusive JavaScript which you can easily achieve by using jQuery's on() method or its shorthand click(). For example:
$(document).ready( function() {
$('.some-class').on('click', slideonlyone('sms_box'));
// OR //
$('.some-class').click(slideonlyone('sms_box'));
});
Inside your handler function (e.g. slideonlyone() in this case) you can reference the element that triggered the event (e.g. the div in this case) with the $(this) object. For example, if you need its ID, you can access it with $(this).attr('id').
EDIT
After reading your comment to #fmsf below, I see you also need to dynamically reference the target element to be toggled. As #fmsf suggests, you can add this information to the div with a data-attribute like so:
<div id="some-id" class="some-class" data-target="sms_box">
...
</div>
To access the element's data-attribute you can use the attr() method as in #fmsf's example, but the best practice is to use jQuery's data() method like so:
function slideonlyone() {
var trigger_id = $(this).attr('id'); // This would be 'some-id' in our example
var target_id = $(this).data('target'); // This would be 'sms_box'
...
}
Note how data-target is accessed with data('target'), without the data- prefix. Using data-attributes you can attach all sorts of information to an element and jQuery would automatically add them to the element's data object.
Why do you need to attach it to the HTML? Just bind the function with hover
$("div.system_box").hover(function(){ mousin },
function() { mouseout });
If you do insist to have JS references inside the html, which is usualy a bad idea you can use:
onmouseover="yourJavaScriptCode()"
after topic edit:
<div class="system_box" data-target="sms_box">
...
$("div.system_box").click(function(){ slideonlyone($(this).attr("data-target")); });
You can bind the mouseenter and mouseleave events and jQuery will emulate those where they are not native.
$("div.system_box").on('mouseenter', function(){
//enter
})
.on('mouseleave', function(){
//leave
});
fiddle
note: do not use hover as that is deprecated
There's several things you can improve upon here. To start, there's no reason to use an <a> (anchor) tag since you don't have a link.
Every element can be bound to click and hover events... divs, spans, labels, inputs, etc.
I can't really identify what it is you're trying to do, though. You're mixing the goal with your own implementation and, from what I've seen so far, you're not really sure how to do it. Could you better illustrate what it is you're trying to accomplish?
== EDIT ==
The requirements are still very vague. I've implemented a very quick version of what I'm imagining you're saying ... or something close that illustrates how you might be able to do it. Left me know if I'm on the right track.
http://jsfiddle.net/THEtheChad/j9Ump/
I'm trying to implement a simple horizontal navigation menu that just shows a single div for each link. It is kinda like a dropdown menu but instead of a mouseover triggering a dropdown, an onclick event will trigger the showing of a div. I want to make sure I am taking the right approach before going too much further, any help is appreciated. This is what I have so far:
<ul id="settings_nav">
<li>
<a>Theme</a>
<div id="settings_block"><%= render :partial => 'email_password' %></div>
</li>
<li>
Lists
<div id="settings_block"><%= render :partial => 'lists' %></div>
</li>
</ul>
window.onload = function(){
settingsMenuInit('settings_nav')
}
function settingsMenuInit(settings_nav){
$(settings_nav).childElements().each(
function(node){
node.onclick= function(){ this.next.show() };
})
}
Something like that, but I am unsure how to get the div that is currently shown and hide it. I could iterate through all the childElements and hide each div and then show the one that is being clicked, but maybe there's a better way?
Some notes FW(T)W:
With Prototype and similar libraries, you don't want to hook up event handlers by assigning functions to the element's onclick and similar properties; that style has several disadvantages (not least that there can only be one handler for the event on the element). Instead, use Prototype's observe function:
someElement.observe('click', functionRefHere);
// or
Element.observe(someElementOrID, 'click', functionRefHere);
This also lets Prototype work around some IE memory loss bugs for you.
You might look at is Prototype's dom:loaded event, which happens sooner than window.onload (which won't happen until all of your images and other external resources have loaded, which can be a second or two after the page is displayed):
document.observe('dom:loaded', initFunctionRefHere);
You can use event delegation and just watch your settings_nav element, rather than each child node individually.
$(settings_nav).observe('click', handleNavClick);
function handleNavClick(event) {
var elm = event.findElement("some CSS selector here");
if (elm) {
event.stop();
// Handle it
}
}
As you can see, Event#findElement accepts a CSS selector. It starts with the actual element that was clicked and tries to match that with the selector; if it matches, it returns the element, otherwise it goes to the parent to see if it matches; etc. So with your HTML you might look for a li (event.findElement('li')) or the link (event.findElement('a')).
But if you want to watch each one individually, they can share a function (as they do in your example):
$(settings_nav).childElements().invoke('observe', 'click', handleNavClick);
function handleNavClick(event) {
// Prototype makes `this` reference the element being observed, so
// `this` will be the `li` element in this example.
}
Whether you watch each element individually or use event delegation depends on what you're doing (and personal preference). Whenever anything is likely to change (adding and removing navigation li elements, for instance) or when there are lots of things to watch, look to event delegation -- it's much easier simpler to deal with changing sets of elements using event delegation and just watching the parent. When dealing with a stable structure of just a few things (as in your example), it may be simpler to just watch the elements individually.
Once inside your handler, you can use Element#down to find child elements (so from the li, you might use li.down('div') to find the div), or Element#next to get to the next sibling element (e.g., going from the link to the div). Either way, once you have a reference to the div, you can use Element#show and Element#hide (or Element#toggle).
I recommend using named functions instead of anonymous ones (see my example above). Named functions help your tools (debuggers, browsers showing errors, etc.) help you. Just be sure not to declare a named function and use it as an expression (e.g., don't immediately assign it to something):
// Don't do this because of browser implementation bugs:
someElement.observe('click', function elementClickHandler(event) {
// ...
});
// Do this instead:
someElement.observe('click', elementClickHandler);
function elementClickHandler(event) {
// ...
}
...because although you should be able to do that according to the spec, in reality various bugs in various browsers make it not work reliably (article).
I write a lot of dynamically generated content (developing under PHP) and I use jQuery to add extra flexibility and functionality to my projects.
Thing is that it's rather hard to add JavaScript in an unobtrusive manner. Here's an example:
You have to generate a random number of div elements each with different functionality triggered onClick. I can use the onclick attribute on my div elements to call a JS function with a parameter but that is just a bad solution. Also I could generate some jQuery code along with each div in my PHP for loop, but then again this won't be entirely unobtrusive.
So what's the solution in situations like this?
You need to add something to the divs that defines what type of behaviour they have, then use jQuery to select those divs and add the behaviour. One option is to use the class attribute, although arguably this should be used for presentation rather than behaviour. An alternative would be the rel attribute, but I usually find that you also want to specify different CSS for each behaviour, so class is probably ok in this instance.
So for instance, lets assume you want odd and even behaviour:
<div class="odd">...</div>
<div class="even">...</div>
<div class="odd">...</div>
<div class="even">...</div>
Then in jQuery:
$(document).load(function() {
$('.odd').click(function(el) {
// do stuff
});
$('.even').click(function(el) {
// dostuff
});
});
jQuery has a very powerful selector engine that can find based on any CSS based selector, and also support some XPath and its own selectors. Get to know them! http://docs.jquery.com/Selectors
I would recommend that you use this thing called "Event delegation". This is how it works.
So, if you want to update an area, say a div, and you want to handle events unobtrusively, you attach an event handler to the div itself. Use any framework you prefer to do this. The event attachment can happen at any time, regardless of if you've updated the div or not.
The event handler attached to this div will receive the event object as one of it's arguments. Using this event object, you can then figure which element triggered the event. You could update the div any number of times: events generated by the children of the div will bubble up to the div where you can catch and handle them.
This also turns out to be a huge performance optimization if you are thinking about attaching multiple handlers to many elements inside the div.
I would recommend disregarding the W3C standards and writing out HTML-properties on the elements that need handlers attached to them:
Note: this will not break the rendering of the page!
<ul>
<li handler="doAlertOne"></li>
<li handler="doAlertTwo"></li>
<li handler="doAlertThree"></li>
</ul>
Declare a few functions:
function doAlertOne() { }
function doAlertTwo() { }
function doAlertThree() { }
And then using jQuery like so:
$("ul li").each(function ()
{
switch($(this).attr("handler"))
{
case "doAlertOne":
doAlertOne();
break;
case ... etc.
}
});
Be pragmatic.
It's a bit hard to tell from your question, but perhaps you can use different jQuery selectors to set up different click behaviours? For example, say you have the following:
<div class="section-1">
<div></div>
</div>
<div class="section-2">
<div></div>
</div>
Perhaps you could do the following in jQuery:
$('.section-1 div').onclick(...one set of functionality...);
$('.section-2 div').onclick(...another set of functionality...);
Basically, decide based on context what needs to happen. You could also select all of the divs and test for some parent or child element to determine what functionality they get.
I'd have to know more about the specifics of your situation to give more focused advice, but maybe this will get you started.
I haven't don't really know about JQuery, but I do know that the DOJO toolkit does make highly unobtrusive Javascript possible.
Take a look at the example here: http://dojocampus.org/explorer/#Dojo_Query_Adding%20Events
The demo dynamically adds events to a purely html table based on classes.
Another example is the behaviour features, described here:http://dojocampus.org/content/2008/03/26/cleaning-your-markup-with-dojobehavior/