I have a javascript class (ES6) and I'm trying to assign a function from that class to a button created by the class. This my main class
class tabDrawer {
constructor(bodyID) {
this.bodyID = bodyID;
this.tabArray = [];
this.initialized = false;
}
get getBodyID() {
return this.bodyID;
}
initialize() {
this.body = document.getElementById(this.bodyID);
var baseHTML = "<div id='tabDiv'><div class='tab'></div><div class='tabContentDiv'></div></div>";
this.body.innerHTML = baseHTML;
this.initialized = true;
}
addTab(tab) {
if(this.initialized) {
var tabClass = "tabLinks";
if(this.tabArray.length === 0) {
tabClass += " active";
}
console.log(this.tabArray.length);
this.body.children[0].children[0].innerHTML += "<button class='" + tabClass + "' id='btn" + tab.name + "'>" + tab.tabTitle + "</button>";
this.body.children[0].children[1].innerHTML += "<div style='display: none' id='" + tab.name + "' class='tabContent'>" + tab.content + "</div>"
var tabButton = document.getElementById("btn" + tab.name);
tabButton.addEventListener("click", evt => this.openTab(evt));
this.tabArray[this.tabArray.length] = tab;
}
}
openTab(index) {
var tabByIndex = this.tabArray[index];
var i, tabcontent, tablinks;
// Get all elements with class="tabcontent" and hide them
tabcontent = document.getElementsByClassName("tabContent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
// Get all elements with class="tablinks" and remove the class "active"
tablinks = document.getElementsByClassName("tabLinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
}
// Show the current tab, and add an "active" class to the button that opened the tab
document.getElementById(tabByIndex.name).style.display = "block";
document.getElementById("btn" + tabByIndex.name).className += " active";
}
}
When a tab gets added (as seen in addTab()) the class creates a button in the tabDrawers body and I want to assign the function openTab(index) which is also in the class. I know I can't just add the event listener with "this.openTab" as it will refer to the button instead of the class.
The line tabButton.addEventListener("click", evt => this.openTab(evt));seems to work but I'm not sure how to pass the index parameter to the function as I want the function to still be available to the end user as well so they can do this: tabs.openTab(0); with "tabs" being an instance of tabDrawer.
Can this be done? If so, how can I do this?
You should use Document.createElement() to create HTML elements (not innerHTML) then you can assign events to those created elements. Simple as this:
var myClickMeFunction = function() {
alert('you clicked myClickMeFunction');
};
var button = document.createElement('button');
button.classList.add('xxx');
button.textContent = 'Click me';
button.addEventListener('click', myClickMeFunction);
document.body.appendChild(button);
.xxx {
background-color: orange;
}
To pass the index, you should use this.tabArray.length instead of evt in your event. The problem is that the length will change when adding another tab, so you need to scope it:
var self = this;
var handler = function (index){
return function(){
self.openTab(index);
}
}
tabButton.addEventListener("click",handler(this.tabArray.length));
This will execute the handler function, to create the event handler using the index from the parameter.
You could do way simpler tough... The only reason you are needing the index is to grab the tab from a tabArray! Why not simply pass the tab directly? This would make it way simpler:
tabButton.addEventListener("click",()=>this.openTab(tab));
Related
I get the message list from the API and create a dynamic array using javascript. I would like a new page with message details to be started when a specific row is pressed.
How do I implement a call to showMessage () on a specific table row?
var list = document.getElementById("listOfMessage");
init();
function init() {
for (var i = 0; i < messageList.length; i++) {
var message = messageList[i];
var li = document.createElement("li");
var a = document.createElement("a");
var text = document.createTextNode("Nadawca: " + message.fullName);
a.appendChild(text);
a.setAttribute('onclick', showMessage(message));
list.appendChild(li);
//list.innerHTML += "<li><a href="showMessage(message)"><h2>Nadawca: " + message.fullName + "
//</h2></a></li>";
}
//list = document.getElementById("listOfTask");
}
function showMessage(message) {
window.sessionStorage.setItem("message", JSON.stringify(message));
window.location.href = 'message.html';
}
In the code above, the showMessage () function is immediately called when the array is initialized. How to make it run only after clicking on a row?
I could add an id attribute to the (a) or (li) element in the init () function, but how to find it later and use it in this code:
var a = document.getElementById('1');
a.addEventListener('click', function() {
window.sessionStorage.setItem("message", JSON.stringify(messageList[0]));
window.location.href = 'message.html';
});
I found a way to solve this problem.
Using this code fragment, we can call a function for a specific element in a dynamically created list.
function init() {
for (var i = 0; i < messageList.length; i++) {
var message = messageList[i];
list.innerHTML += "<li id="+i+"><a onClick="+
"><h2>Nadawca: " + message.fullName + "</h2></a></li>";
}
//$(document).on("click", "ui-content", function(){ alert("hi"); });
$(document).ready(function() {
$(document).on('click', 'ul>li', function() {
var idName = $(this).attr('id');
showMessage(messageList[idName]);
});
});
}
I currently have a JavaScript that is looking at a SharePoint list, and pulling back all of the items that meet the criteria in the REST call.
It currently creates DIVs and appends them to a wrapper DIV. The intention of the button is to show/hide the sub-DIVs.
Right now, when I click any of the buttons that are produced, it expands all of the hidden divs. What I'm trying to accomplish is to be able to click each respective button and have its nested div show/hide.
Here is my code:
var listName = "announcement";
var titleField = "Title";
var tipField = "Quote";
var dateFieldFrom = "DateFrom";
var dateFieldTo = "DateTo";
var category = "category";
var noteField = "note";
var query = "/_api/Web/Lists/GetByTitle('" + listName + "')/items?$select=" + titleField + "," + dateFieldTo + "," + dateFieldFrom + "," + category + "," + noteField + "," + tipField;
var today = new Date();
var btnClass = "toggle"
todayString = today.getFullYear() + "-" + (today.getMonth() + 1) + "-" + today.getDate();
//This is the query filter string where we compare the values in the 2 date fields against the current date
query += "&$filter=('" + todayString + "' ge " + dateFieldFrom + " ) and (" + dateFieldTo + " ge '" + todayString + "')";;
var call = $.ajax({
url: _spPageContextInfo.webAbsoluteUrl + query,
type: "GET",
dataType: "json",
headers: {
Accept: "application/json;odata=verbose"
}
});
call.done(function(data, textStatus, jqXHR) {
var divCount = data.d.results.length;
for (var i = 0; i < divCount; i++) {
var tip = data.d.results[i][tipField]; //this is where it looks at the quote field to determine what quote to place in the current dynamically created DIV
var cat = data.d.results[i][category]; //this is where it looks at the category field to determine what color to style the background of the current dynamically created DIV
var message = data.d.results[i][noteField];
var ID = "NewDiv-" + i
var PID = "P-" + i
var BID = "btn-" + i
// Create Message DIV
var element = document.createElement("div"); //This is the creation of the dynamic DIV
element.id = ID //This is assigning a DIV an ID
element.appendChild(document.createTextNode(tip));
// Create Inner message DIV
var innerDiv = document.createElement("div"); // Create a <div> element//New Code
innerDiv.id = PID
innerDiv.appendChild(document.createTextNode(message));
// Create button to show/hide the div
var btn = document.createElement("BUTTON");
btn.id = BID
btn.appendChild(document.createTextNode("show/hide message below"));
btn.className = btnClass
// Append Inner DIVs
document.getElementById('wrapper').appendChild(element); //This is the parent DIV element that all newly created DIVs get created into
document.getElementById(ID).appendChild(btn); // Append the button to the newly created DIV
document.getElementById(ID).appendChild(innerDiv); //This is the message that appears after the newly created DIVs
if (cat == 'Information') {
document.getElementById(ID).style.backgroundColor = '#d9edf7'; //Blue Color
document.getElementById(PID).style.backgroundColor = '#d9edf7'; //Blue Color
document.getElementById(PID).style.margin = '3px';
document.getElementById(BID).style.backgroundColor = '#d9edf7';
document.getElementById(BID).style.border = 'none';
innerDiv.className = "alert alert-info"
element.className = "alert alert-info"
}
if (cat == 'Warning') {
document.getElementById(ID).style.backgroundColor = '#fcf8e3'; //Orange Color
document.getElementById(PID).style.backgroundColor = '#fcf8e3'; //Orange Color
document.getElementById(PID).style.margin = '3px';
document.getElementById(BID).style.backgroundColor = '#fcf8e3';
document.getElementById(BID).style.border = 'none';
innerDiv.className = "alert alert-warning"
element.className = "alert alert-warning"
}
if (cat == 'Critical') {
document.getElementById(ID).style.backgroundColor = '#f2dede'; //Red Color
document.getElementById(PID).style.backgroundColor = '#f2dede'; //Red Color
document.getElementById(PID).style.margin = '3px';
document.getElementById(BID).style.backgroundColor = '#f2dede';
document.getElementById(BID).style.border = 'none';
innerDiv.className = "alert alert-danger"
element.className = "alert alert-danger"
}
}
// The below variables and for loop ensure that all sub messages are initially hidden, until the show/hide button is clicked
var curDiv
var curID
for (var i = 0; i < divCount; i++) {
curID = "P-" + i
curDiv = document.getElementById(curID)
curDiv.style.display = 'none';
}
// The function below is to assign an event to the button to show/hide the sub message
var f = function(a) {
var cDiv
for (var z = 0; z < divCount; z++) {
cDiv = "P-" + z
var div = document.getElementById(cDiv);
if (div.style.display !== 'none') {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
}
return false;
}
var elems = document.getElementsByClassName("toggle");
var idx
for (var i = 0, len = elems.length; i < len; i++) {
elems[i].onclick = f;
}
});
<div id="wrapper" class="header"> </div>
You're assigning all of your buttons the same onclick function event handler, and that function loops through all the divs and shows them or hides them.
An alternative approach would be to have the event handler toggle only the specific div that's associated with the button.
When you first create the button, you can assign an event handler to it immediately and pass in a reference to the div you want to hide:
var innerDiv = document.createElement("div");
innerDiv.id = PID
innerDiv.appendChild(document.createTextNode(message));
var btn = document.createElement("BUTTON");
// Immediately-invoked function expression to attach event handler to inner div:
(function(d){
btn.onclick = function(){ f(d); };
})(innerDiv);
Then just update your f function to accept as a parameter the div you want to toggle.
// The function below is to assign an event to the button to show/hide the sub message
function f(div){
if (div.style.display !== 'none') {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
return false;
}
You can then remove the last few lines of code where you're assigning the buttons to the elems collection and looping through it to attach an onclick function.
You can replace all of this - which loops over all divs
// The function below is to assign an event to the button to show/hide the sub message
var f = function(a) {
var cDiv
for (var z = 0; z < divCount; z++) {
cDiv = "P-" + z
var div = document.getElementById(cDiv);
if (div.style.display !== 'none') {
div.style.display = 'none';
} else {
div.style.display = 'block';
}
}
return false;
}
var elems = document.getElementsByClassName("toggle");
var idx
for (var i = 0, len = elems.length; i < len; i++) {
elems[i].onclick = f;
}
with this, it delegates the click on the button in the wrapper and toggles the next object after the button
$('#wrapper').on("click",".toggle",function(e) { // notice the delegation
e.preventDefault(); // in case you forget type="button"
$(this).next().toggle();
});
Like this:
$(function() {
$('#wrapper').on("click", ".toggle", function(e) {
e.preventDefault();
$(this).next().toggle();
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.min.js"></script>
<div id="wrapper">
<div id="NewDiv-0" class="alert alert-info" style="background-color: rgb(217, 237, 247);">Debbie Teng joins PD Tax!********
<button id="btn-0" class="toggle" style="background-color: rgb(217, 237, 247); border: none;">show/hide message below</button>
<div id="P-0" class="alert alert-info" style="background-color: rgb(217, 237, 247); margin: 3px; display: none;">yadayada1</div>
</div>
</div>
Title, my only problem is that when I've created all elements on my page, and clicked all of them, my page looks like a chess board.
I can only "toggle" the background color of half too. So it's not only that they don't change color on the first click, they don't change at all.
This is my Javascript:
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div" + i);
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!";
for (let i = 0; i < 10; i++) {
$('div' + i).click(function() {
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
}
document.getElementById("page").appendChild(itemContainer);
}
I made a JSFiddle for you who want it.
I've seen a few other questions about how to toggle the color of backgrounds, but none of them have the same problem as me.
You inserted your second loop into the first one, every second i got skipped. And probably was able to change your divs up to i=18
JSFiddle
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div" + i);
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!";
document.getElementById("page").appendChild(itemContainer);
}
for (let i = 0; i < 10; i++) {
$('div' + i).click(function() {
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
}
Edit: You could simply put the content of your second loop into the first loop, to simplify your code a bit.
You don't need 2 loops try that
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div");
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!";
document.getElementById("page").appendChild(itemContainer);
$('#div' + i).click(function() {
alert("here");
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
}
fiddle example
You were close, missing "#" of id element
$('div' + i).click(function() {
$('#div' + i).click(function() {
and you have inserted the second loop inside first one
https://jsfiddle.net/snbtchph/
Your selector at line 8 of your JavaScript is missing the # so the jQuery is looking for <div0>, <div1>, <div2>..., and, your line 2 of JavaScript is var itemContainer = document.createElement("div" + i); which actual creating elements div0, div1....
And since you are using jQuery , I have also revised some code to use it instead of native JavaScript: https://jsfiddle.net/xfr496p6/5/
I have also added css .item { display: inline-block; } to makes the elements placed in a row.
There are a few problems with your code:
var itemContainer = document.createElement("div" + i);
Creating non-existant elements like <div1> is impossible, remove the iterator.
for (let i = 0; i < 10; i++) {
jQuery's .click() doesn't need a for loop, but adds the event listener to every case, this is not needed.
document.getElementById("page").appendChild(itemContainer);
Apply this directly in after the .innerHTML
In addition, you seem to randomly use ES6, jQuery, and VanillaJS through your entire codebase, I'd like to advise you to be consistant with how you write your applications.
I've updated your fiddle with the working changes.
https://jsfiddle.net/xfr496p6/8/
Updated javascript:
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div");
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!" + i;
document.getElementById("page").appendChild(itemContainer);
}
$('div').click(function() {
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
Why do you have 2 nested loops?
try this
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div" + i);
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!";
document.getElementById("page").appendChild(itemContainer);
}
for (let i = 0; i < 10; i++) {
$('div' + i).click(function() {
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
}
JSFIDDLE
for (var i = 0; i < 10; i++) {
var itemContainer = document.createElement("div" + i);
itemContainer.id = "div" + i;
itemContainer.className = "item";
itemContainer.innerHTML = "Hello!";
$(itemContainer).click(function() {
if (this.className == "item") {
this.className = "itemselected";
} else {
this.className = "item";
}
});
document.getElementById("page").appendChild(itemContainer);
}
I have the following piece of code I am working on. My purpose is to be able to grab information about different users from a specific website, display the name and other info and then have a button that when clicked, prints more information. I am able to get the information and display the name and picture, but when I click the button the information is displayed at the top of the page, not under the specific button that was clicked. I want for the information to be display under each user... I am new to Javascript and learning on my own, any help is appreciated!
function getUsers(user) {
var out = "";
var i;
for (i = 0; i < user.length; i++) {
out += '' + user[i].login + '<br>'+'</br> <img src="'+user[i].avatar_url+
'" alt="Image" style="width:304px;height:228px"</br></br>'+
'<button onclick=printRepos("'+user[i].repos_url+'")>Repositories</button></br>'+'<div id="id"></div>';
}
document.getElementById("id01").innerHTML = out;
}
Printing Function
function printF(array) {
var out = "";
var i;
for (i = 0; i < array.length; i++) {
out += array[i].id+'</br>';
}
document.getElementById("id").innerHTML = out;
}
This works fine. I just made div with dynamic ids and passed it to the function
function getUsers(user) {
var out = "";
var i;
for (i = 0; i < user.length; i++) {
out += '' + user[i].login + ' <br>'+'</br> <img src="'+user[i].avatar_url+
'" alt="Image" style="width:304px;height:228px"</br></br>'+
'<button onclick=printRepos("'+user[i].repos_url+'","'+i+'")>Repositories</button></br>'+'<div id="'+ 'id' + i +'"></div>';
}
document.getElementById("id01").innerHTML = out;
}
function printRepos(array, id) {
var out = "";
var i;
for (i = 0; i < array.length; i++) {
out += array[i].id+'</br>';
}
console.log('id' + id);
document.getElementById('id' + id).innerHTML = out;
}
Add the "this" keyword as a parameter to your onclicks, to pass in the button that was clicked:
<button onclick=printRepos(this,"'+user[i].repos_url+'")>Repositories</button>
Then locate the next div after that button in your event handler:
function printF(btn, array) {
var out = "";
var i;
for (i = 0; i < array.length; i++) {
out += array[i].id+'</br>';
}
// find the div
var d = btn; // start with the button
while (d.tagName != "DIV") d = d.nextSibling; // look for the next div
d.innerHTML = out;
}
I'm trying to pass a parameter in the onclick event. Below is a sample code:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>
However whenever I click on any of the links the alert always shows 'Link 10 clicked'!
Can anyone tell me what I'm doing wrong?
Thanks
This happens because the i propagates up the scope once the function is invoked. You can avoid this issue using a closure.
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick = (function() {
var currentI = i;
return function() {
onClickLink(currentI + '');
}
})();
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
Or if you want more concise syntax, I suggest you use Nick Craver's solution.
This is happening because they're all referencing the same i variable, which is changing every loop, and left as 10 at the end of the loop. You can resolve it using a closure like this:
link.onclick = function(j) { return function() { onClickLink(j+''); }; }(i);
You can give it a try here
Or, make this be the link you clicked in that handler, like this:
link.onclick = function(j) { return function() { onClickLink.call(this, j); }; }(i);
You can try that version here
link.onclick = function() { onClickLink(i+''); };
Is a closure and stores a reference to the variable i, not the value that i holds when the function is created. One solution would be to wrap the contents of the for loop in a function do this:
for (var i = 0; i < 10; i++) (function(i) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.onclick= function() { onClickLink(i+'');};
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}(i));
Try this:
<div id="div"></div>
<script language="javascript" type="text/javascript">
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var f = function() {
var link = document.createElement('a');
var j = i; // this j is scoped to our anonymous function
// while i is scoped outside the anonymous function,
// getting incremented by the for loop
link.setAttribute('href', '#');
link.innerHTML = j + '';
link.onclick= function() { onClickLink(j+'');};
div.appendChild(link);
div.appendChild(document.createElement('br')); // lower case BR, please!
}(); // call the function immediately
}
function onClickLink(text) {
alert('Link ' + text + ' clicked');
return false;
}
</script>
or you could use this line:
link.setAttribute('onClick', 'onClickLink('+i+')');
instead of this one:
link.onclick= function() { onClickLink(i+'');};
Another simple way ( might not be the best practice) but works like charm. Build the HTML tag of your element(hyperLink or Button) dynamically with javascript, and can pass multiple parameters as well.
// variable to hold the HTML Tags
var ProductButtonsHTML ="";
//Run your loop
for (var i = 0; i < ProductsJson.length; i++){
// Build the <input> Tag with the required parameters for Onclick call. Use double quotes.
ProductButtonsHTML += " <input type='button' value='" + ProductsJson[i].DisplayName + "'
onclick = \"BuildCartById('" + ProductsJson[i].SKU+ "'," + ProductsJson[i].Id + ")\"></input> ";
}
// Add the Tags to the Div's innerHTML.
document.getElementById("divProductsMenuStrip").innerHTML = ProductButtonsHTML;
It is probably better to create a dedicated function to create the link so you can avoid creating two anonymous functions. Thus:
<div id="div"></div>
<script>
function getLink(id)
{
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = id;
link.onclick = function()
{
onClickLink(id);
};
link.style.display = 'block';
return link;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i += 1)
{
div.appendChild(getLink(i.toString()));
}
</script>
Although in both cases you end up with two functions, I just think it is better to wrap it in a function that is semantically easier to comprehend.
onclick vs addEventListener. A matter of preference perhaps (where IE>9).
// Using closures
function onClickLink(e, index) {
alert(index);
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.innerHTML = i + '';
link.addEventListener('click', (function(e) {
var index = i;
return function(e) {
return onClickLink(e, index);
}
})(), false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
How abut just using a plain data-* attribute, not as cool as a closure, but..
function onClickLink(e) {
alert(e.target.getAttribute('data-index'));
return false;
}
var div = document.getElementById('div');
for (var i = 0; i < 10; i++) {
var link = document.createElement('a');
link.setAttribute('href', '#');
link.setAttribute('data-index', i);
link.innerHTML = i + ' Hello';
link.addEventListener('click', onClickLink, false);
div.appendChild(link);
div.appendChild(document.createElement('BR'));
}
This will work from JS without coupling to HTML:
document.getElementById("click-button").onclick = onClickFunction;
function onClickFunction()
{
return functionWithArguments('You clicked the button!');
}
function functionWithArguments(text) {
document.getElementById("some-div").innerText = text;
}