There are 5 buttons where each button will do a different thing.
The first button will change a paragraph to green (in HTML file, Id="button1").
The second button will change a paragraph to blue (in HTML file, Id="button2").
window.onload = pageLoad;
function pageLoad() {
this.onclick = makeChange;
}
function makeChange() {
var paragraph = document.getElementById("paragraph");
if (this.id = "button1") {
paragraph.style.color = "green";
} else {
// change the color
}
}
This doesn't work because I can't get the id of the button, I tried to debug it using:
paragraph.innerHTML = this.id;// I got "undefined"
Is there anyway I can get which button is pressed, and based on which button is pressed, change the text color differently? I want to use only 1 function(exclude pageLoad) to do this and I don't to have 5 variableS and 5 onclick lines and no jquery.
var paragraph = document.GetElementById("button1");
var paragraph = document.GetElementById("button2");
var paragraph = document.GetElementById("button3");
....
You could try something like this. Keep the color you want to change the paragraph to inside the button, using a custom data-* attribute. Then access it with .getAttribute(). So your JS would be something like this:
function makeChange() {
var paragraph = document.getElementById("paragraph");
var newColor = this.getAttrbiute('data-color');
paragraph.style.color = newColor;
}
And your buttons would be like this:
<button data-color="green">Green</button>
<button data-color="blue">Blue</button>
This way you can easily change the target color by changing the data-color attribute.
As a side note, do make sure you're listening for the click event with your buttons, like this:
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', makeChange);
}
Using an event listener to leverage Event Delegation has a few advantages over an on event (ex. onclick) property or attribute event handler:
Unlike on event handlers, event listeners can be used multiple times on multiple objects and elements.
By setting the 3rd parameter to true, you can listen for events in capture phase. It's rarely needed.
We can setup a pattern to take advantage of the 3 Event.eventPhases:
Capture: Starting from Event.currentTarget (ex. #set) the event chain goes down the DOM tree to the end of this phase is...
Target: The element that is the origin of the event is the Event.target (ex. #btn*).In simpler terms, e.target is the element clicked, checked, changed, etc. This element will be the context related to the event, in many ways e.target is like this (in this demo, e.target is one of the buttons and this is the ancestor (fieldset#set, e.cT)...
Bubbling: This is the phase after the callback is initiated and the event chain reverses its path back up to the Event.currentTarget. On its way up, should there be any other elements registered to this particular event would also be triggered. This may be an unwanted behavior depending on the developer's intentions and circumstances. For those cases, Event.stopPropagation() can be called (usually placed as the last statement in the callback). Upon reaching the end of the bubbling phase, the e.cT calls the callback (this callback will be in the context of e.target, this context allows us to identify the clicked button)
Event Delegation is a process in which we setup our event listener on a higher level in the DOM (an ancestor element), then at target phase the clicked button will be identified by referencing e.target. This gives us a powerful and eloquent way to handle an unlimited amount of elements with a single event listener. No on event handler is capable of doing this under normal circumstances without the use of specialized interface.
More details are commented in demo
Demo
/* Reference an ancestor element of all the buttons.
|| In this case the best choice would be
|| fieldset#set. You could also use <body>, document,
|| or even window, but the further the ancestor is,
|| the chances of unwanted behavior from other elements
|| become greater.
*/
var set = document.getElementById('set');
/* Register #set on the click event.
|| Now #set is considered the Event.currentTarget.
|| The e.cT (#set) will listen for the event (click)
|| to occur upon itself and any of its decsendants.
|| Once event occurs, #set (e.cT) will run the
|| callback function (findBtn).
*/
set.addEventListener('click', findBtn, false);
/* This is the callback function which is a normal
|| function that is called when a registered event
|| happens.
*/
function findBtn(e) {
// An array of 5 colors
var rainbow = ['purple', 'blue', 'green', 'yellow', 'red'];
// Reference to the paragraph (<p>)
var text = document.querySelector('p');
/* if e,target (the element clicked), is NOT
|| e.cT, then...
*/
if (e.target !== e.currentTarget) {
/*...and if e.T (Event.target) tagName is
|| 'BUTTON', then...
*/
if (e.target.tagName === 'BUTTON') {
// ...get the button's #id
var tgtID = e.target.id;
/* The id of each button is basically just
|| an index number. Although I do not
|| recommend it normally, it is valid to start
|| with a number for an #id.
|| This statement uses the style property to
|| access the paragraph's CSS color property
|| and changes the value according to the
|| index of the rainbow array which in turn is
|| determined by the e.T (button clicked) #id
*/
text.style.color = rainbow[tgtID];
}
}
// End function
return false;
}
<fieldset id='set'>
<legend>Event Delegation</legend>
<button id='0'>Purple</button>
<button id='1'>Blue</button>
<button id='2'>Green</button>
<button id='3'>Yellow</button>
<button id='4'>Red</button>
</fieldset>
<p>Click any of the five buttons to change this text.</p>
Related
All,
I have a circle that I drag and drop from the upper div to the bottom div. Please see fiddle. After I click the circle in the bottom I then try to change the height with the input button. The input button is between the top and bottom div and uses incremental values of 5.
However, my event handler doesn't change the height value after incrementing from 5 to 10.
My goal is to allow changing of a height especially after I dropped a circle in the bottom and div and after selecting the circle to simulate activation.
Is there something wrong with how I'm trying to associate the event handler to the circle and the input button?
https://jsfiddle.net/mdevera/ff1bfpsd/
destinationContainer.addEventListener("click", change);
function change(event) {
activeShape = event.srcElement;
}
function changeHeight(event) {
//var height = (parseInt(activeShape.clientHeight, 10) + parseInt(event.target.value, 10)) + "px";
activeShape.style.height = event.target.value + "px";
}
The problem is that your click event listener for the #destination element only calls the change function when it is clicked. In other words, the change function isn't fired when you drag/drop the element since there aren't any click events fired. As a result, the activeShape variable defined in the change function doesn't point to an element since the change function is never called.
Another approach would be to select the last child element of the #destination element using the :last-child pseudo class. The last child is always the last element appended which means that you would always target the most recently dropped element when changing the height.
Updated Example
function changeHeight(event) {
var activeShape = destinationContainer.querySelector(':last-child');
if (activeShape !== null) {
activeShape.style.height = event.target.value + "px";
}
}
After reading your comment:
In addition to the changes above, it also seems like you are looking for the input event rather than the change event. The input event is fired fired synchronously when the value of the element is changed, whereas the change event is fired on blur (which is when focus is removed from that field).
Updated Example
var destinationContainer = document.querySelector("#destination");
var input = document.querySelector('#height-input');
input.addEventListener('input', function (event) {
var activeShape = destinationContainer.querySelector(':last-child');
if (activeShape !== null) {
activeShape.style.height = event.target.value + "px";
}
});
It looks unusual, but you should move out of input after changes made or use keys up/down to "catch up" input changes. In general it's some kind of onblur needed. In modern browser oninput event is preferable one for such cases.
onchange - only after focus out
oninput - the same as onchange but do not need blur focus
onpropertychange -
Just change onchange in your HTML to oninput and your problem will be resolved.
I'm trying to add a click listener to the document when a dropdown is open, so that when you click anywhere else in the document it closes the dropdown and unbinds the click listener. I'm using some code from https://stackoverflow.com/a/17342418
I've discovered an issue though.
Binding:
$(document).bind('click', $scope.remindersDropdownHandler(event));
Handler:
$scope.remindersDropdownHandler = function(event) {
var element = $("#remindersDropdown");
var isClickedElementChildOfPopup = element
.find(event.target)
.length > 0;
var buttonElement = $("#remindersButton");
var isbuttonElement = buttonElement
.find(event.target)
.length > 0;
if (isClickedElementChildOfPopup || isbuttonElement) {
return;
}
$scope.openReminders = false;
// $(document).unbind('click', $scope.remindersDropdownHandler);
}
The handler itself is working just fine, however it only ever executes once! And that's immediately when you click the button that opens the drop-down and adds the bind. After that no matter where you click it never fires the listener again. (Trust me I've done a lot of debugging with console.log() over the past hour.)
However, if instead of referencing a function for the handler, I instead write it like such:
$(document).bind('click', function() {
var element = $("#remindersDropdown");
var isClickedElementChildOfPopup = element
.find(event.target)
.length > 0;
var buttonElement = $("#remindersButton");
var isbuttonElement = buttonElement
.find(event.target)
.length > 0;
if (isClickedElementChildOfPopup || isbuttonElement) {
return;
}
$scope.openReminders = false;
});
That works, and each time I click somewhere on the document it correctly executes the handler and closes the drop-down. The problem is because I haven't referenced the handler via a variable, I can't unbind it, so the handler is permanently attached. I need to be able to unbind it so that my site doesn't get overloaded with click listeners on the document.
So my issues are:
1) When I reference $scope.remindersDropdownHandler() as the handler for my click listener, why does it only fire once and then never again?
2) How can I fix up my code so that I bind the appropriate function for the click listener, and then unbind it when I choose to do so?
Note: In the example code I'd commented out the // $(document).unbind('click', $scope.remindersDropdownHandler); just to reinforce that there was no code that was immediately unbinding my click listener.
Solved: A user on Reddit pointed out that removing (event) from the specified handler in the bind would allow it to work.
https://www.reddit.com/r/angularjs/comments/3fypck/help_issue_with_binding_and_unbinding_a_click/cttr0bw
I.e. converting
$(document).bind('click', $scope.remindersDropdownHandler(event));
to
$(document).bind('click', $scope.remindersDropdownHandler);
I am trying to figure out a way to easily disable/enable buttons within dat.gui.
I have dat.gui set up to control an animation. When the animation reaches its end, I want the "Play" button to become disabled. I have tried adding a "disabled" attribute to the DOM elements of the button, but I am still seeing the corresponding function fire when the button is clicked after this attribute is set.
My current method is the following:
Locate the li element that corresponds to the button in the dat.gui interface
Create a new DOM element that is semi-transparent and black, and add this inside the li element to gray out the contents of the button.
In the function bound to this button, check for the existence of this "disabled" DOM element within the button, and if it exists, refrain from executing the rest of the function.
This is a hack, and I would love to know if there was some method for disabling a button built right into dat.gui, or some better method that I am not aware of.
In dat.GUI the FunctionController class is responsible for buttons. If you look at its source code, there is no conditional logic in there. The controller will listen to click events on the button and it will always call the function on click. This means that you won't get any help from the library here - you need to check in the handler whether the button is disabled. Something along these lines:
// Find the GUI controller listening to given property on given object
function getController(gui, object, property)
{
for (var i = 0; i < gui.__controllers.length; i++)
{
var controller = gui.__controllers[i];
if (controller.object == object && controller.property == property)
return controller;
}
return null;
}
...
object.button = function()
{
// Don't do anything if the button is disabled
if (getController(gui, this, "button").domElement.hasAttribute("disabled"))
return;
alert("Button clicked");
};
gui.add(object, "button");
...
// Disable button
getController(gui, object, "button").domElement.setAttribute("disabled", "");
Note that there is no special styling for disabled elements in dom.GUI, you would have to add you own styles for that. Given that what you see in case of a button is the property label rather than the actual button this isn't going to be quite trivial - I think you will have to place the disabled attribute on controller.domElement.parentNode rather than controller.domElement. Then you should be able to use the selector [disabled] > .property-name for your styles.
Edit: You can actually do this in a more generic way by extending FunctionController:
function blockEvent(event)
{
event.stopPropagation();
}
Object.defineProperty(dat.controllers.FunctionController.prototype, "disabled", {
get: function()
{
return this.domElement.hasAttribute("disabled");
},
set: function(value)
{
if (value)
{
this.domElement.setAttribute("disabled", "disabled");
this.domElement.addEventListener("click", blockEvent, true);
}
else
{
this.domElement.removeAttribute("disabled");
this.domElement.removeEventListener("click", blockEvent, true);
}
},
enumerable: true
});
This will add a property disabled to the controller that will catch click events so that the button handler isn't triggered. Disabling the button gets simpler then:
getController(gui, object, "button").disabled = true;
And the button handler can stay unchanged, it simply won't be triggered for disabled buttons.
Here the most straightforward way I could think of to disable a single element of dat GUI:
let gui = new dat.GUI();
let uiElement = gui.add(myObject, 'myPropertyName');
uiElement.__li.style = "opacity: 0.5; filter: grayscale(100%) blur(1px); pointer-events: none;";
Old browsers may not support pointer-events: none; so optionally you can add:
disableAll(uiElement.__li);
function disableAll(element){
for( var i = 0; i < element.childNodes.length; ++i){
let elt = element.childNodes[i];
disableAll(elt);
elt.disabled = true;
}
}
This may look "hacky" but in the official dat GUI API there is no such function and even if it was in there, it would most likely do something very similar.
Lastly, through the API you can entirely delete an element:
uiElement.remove();
I've got a strange problem on Firefox that seems not to happen on Safari.
There's a table with a set of rows, each one of which has it's own onclick and ondblclick events. When one of the objects is double-clicked, it fires first the onclick associated function (as expected), where another row (different from the one double-clicked) is deleted. Afterwards, the function associated with dblclick won't fire.
If I comment the line which removes the row (not the one clicked, as I said, but another one), then both the onclick and ondblclick events will fire... I attach you the code for both event functions:
ret.onclick = function(){
// Trigger click event
var evt = arguments[0] || window.event;
self.signalClick(evt.target || evt.srcElement);
if(elem == this.selected) return;
if(self.selected != null){
// Set list element to not selected
var telem = document.getElementById(self.getChildID(self.selected['id']));
telem.setAttribute('class', 'gui_list_uselected');
// Remove previously selected element summary
var telemexp = document.getElementById(self.getChildID(self.selected['id']) + '_exp');
if(telemexp) telemexp.parentNode.removeChild(telemexp); // FAULTY LINE!
}
ret.setAttribute('class', 'gui_list_selected');
self.selected = elem;
// Add element summary to the list
appendAfter(ret, self.drawSummary(elem));
};
ret.ondblclick = function(){
// Trigger double click event
var evt = arguments[0] || window.event;
self.signalDblClick(evt.target || evt.srcElement);
};
Firefox works correctly. According to the spec, onclick fires before ondblclick anyway.
Check out this so answer to overcome that.
replace for loop with a switch statement somehow?
You don't have to loop over all radio buttons to find the clicked one. You can pass the clicked element directly to your function:
function planeChoice(element) {
// element refers to the clicked radio button
var plane = element.value;
switch (plane) {
//...
}
}
For that to work, you have to pass this to your function:
<input type="radio" name="planeButton" value="152"
onclick="planeChoice(this)" />
this refers to the HTML element you attach the event handler to, so in this case it refers to the <input> element.
To learn more about events, I suggest to read the articles on http://quirksmode.org, starting with Introduction to Events and Early event handlers.
Two suggestions for further improvement:
(A) You can use a map (which is just an plain object in JavaScript) instead of a switch statement to determine the corresponding message:
var map = {
"152": "A small two-place-airplane for flight training",
"172": "The smaller of two four-place airplanes"
// ...
};
A map is also easier to maintain (to extend).
Once you have the value of the radio button, you can access the message with:
alert(map[plane]);
You are not limited to store only primitive values (like strings), you can also store functions and call them if you want to do some more complex things. But to learn more about functions and how you can use them, you should read a JavaScript guide.
(B) You can use event delegation instead of binding the same event handler to every element (this works through event bubbling). The click event handler is attached to the <form> element:
<form onclick="planeChoice(event)" ...>
Or even better, get a reference to the form element and attach the event handler via JavaScript:
document.getElementById("myForm").onclick = planeChoice;
The passed event object holds information about which element was clicked:
function planeChoice (event) {
event = event || window.event; // for IE
var target = event.target || event.srcElement; // for IE
if(target.type === "radio") { // if a radio button is clicked
var plane = target.value;
// ... further code
}
}
Can I suggest you try using jQuery? It's a useful (and popular) JavaScript library that will help reduce and simplify the code you need.
For example, the above code could be simplified to this in jQuery:
$('#myForm input:radio').click(function(){
switch (this.value) {
case "152":
alert("A small two-place-airplane for flight training");
break;
// More Options go here...
default:
alert("Error in JavaScript function planeChoice");
break;
}
});
It would also eliminate the need to use click handlers on each radio button.