Dynamically create buttons in js/html based on object state - javascript

I have the following situation which I cannot solve. I am relatively new to js. I have a js that runs on a webpage. The script runs when a KB shortcut is pressed. After modifying a few things, it pops up an html banner in which I want to put certain messages and buttons depending on what thing the user ran a script on. For a simple case, let's say there are two potential messages that can go in this popup. I have the details in an object array:
NH_Bann = {
STC: {
active: false,
bannText: "Force Title Case: ",
id: "toTitleCaseStrong",
value: "Yes",
action: function() {
var newNameStr = toTitleCaseStrong(vname);
if (newNameStr !== name) {
//**update function
NH_Bann.STC.active = false;
}
}
},
DTC: {
active: false,
bannText: "Enable DTC? ",
id: "addDTC",
value: "Yes",
action: function() {
//**update function
NH_Bann.DTC.active = false;
}
}
}
When the script is run, there are some if statments that can change the active keys to true. After the script runs, I want to run through the objects in NH_Bann, and if the active key is true, make a message with an action button that fires the action button. The part I am having trouble with is making the buttons dynamically. From other threads, I thought I could store the buttons in an array, but maybe the onclick doesn't work that way? This is what I have:
function setupButtons() {
var ixButt = 0;
var btn = [];
for (var NHix = 0; NHix < Object.keys(NH_Bann).length; NHix++ ) {
tempKey = Object.keys(NH_Bann)[NHix];
if (NH_Bann[tempKey].active) {
btn[ixButt] = document.getElementById(NH_Bann[tempKey].id);
btn[ixButt].onclick = function(){
NH_Bann[tempKey].action();
assembleBanner(); // makes the html for the banner
}
ixButt++;
}
}
}
I make the buttons in another piece of code which sets up the ids:
function assembleBanner() {
sidebarMessageEXT = [sidebarMessage.slice(0)];
var EXTOption = false;
for (var NHix = 0; NHix < Object.keys(NH_Bann).length; NHix++ ) {
tempKey = Object.keys(NH_Bann)[NHix];
if (NH_Bann[tempKey].active) {
sidebarMessageEXT.push(NH_Bann[tempKey].bannText + '<input id="' + NH_Bann[tempKey].id + '" type="button" value="' + NH_Bann[tempKey].value + '">');
EXTOption = true;
}
}
if (EXTOption) {
sidebarMessageEXT = sidebarMessageEXT.join("<li>");
displayBanners(sidebarMessageEXT,severity);
setupButtons();
} else {
displayBanners(sidebarMessage,severity);
setupButtons();
}
}
The issue i'm having is that I get the two distinct messages and two buttons in the banner if both objects are active==true, but pressing them always fires the update function of the DTC object. Any suggestions? I'm open to other methods, but I need to be able to add to the object list over time and have the buttons be displayed conditionally on the status of the active key for each object. Thx!

The problem has to do with closures. In this code:
function setupButtons() {
var ixButt = 0;
var btn = [];
for (var NHix = 0; NHix < Object.keys(NH_Bann).length; NHix++ ) {
tempKey = Object.keys(NH_Bann)[NHix];
if (NH_Bann[tempKey].active) {
btn[ixButt] = document.getElementById(NH_Bann[tempKey].id);
btn[ixButt].onclick = function(){
NH_Bann[tempKey].action();
assembleBanner(); // makes the html for the banner
}
ixButt++;
}
}
}
...tempKey is a variable that lives within the call to setupButtons. Notice that you're creating two onclick function callbacks in a loop, and both make reference to tempKey. However, they're not going to be referencing the variable's value at the time of function creation, but rather the latest value of the variable. So once the loop completes, tempKey is going to reference the last value it had.
To work around this, you can use this trick to create a new closure for each onclick that will have the correct value:
function setupButtons() {
var ixButt = 0;
var btn = [];
for (var NHix = 0; NHix < Object.keys(NH_Bann).length; NHix++ ) {
tempKey = Object.keys(NH_Bann)[NHix];
if (NH_Bann[tempKey].active) {
btn[ixButt] = document.getElementById(NH_Bann[tempKey].id);
btn[ixButt].onclick = (function(buttonId) {
return function() {
NH_Bann[buttonId].action();
assembleBanner(); // makes the html for the banner
}
})(tempKey);
ixButt++;
}
}
}
Essentially, this is binding the current value of tempKey to a new variable by passing it to an immediately-executing function, so both onclick functions no longer reference the variable which changes during the loop.
For a less esoteric way to do this, you could move the creation of each button into its own named function, passing the required data:
function setupButtons() {
var btn = [];
for (var NHix = 0; NHix < Object.keys(NH_Bann).length; NHix++) {
tempKey = Object.keys(NH_Bann)[NHix];
if (NH_Bann[tempKey].active) {
btn.push(setupButton(NH_Bann[tempKey]);
}
}
}
function setupButton(bannerData) {
var button = document.getElementById(bannerData.id);
button.onclick = function() {
bannerData.action();
assembleBanner();
};
return button;
}

Related

element.onclick not doesn't call function when element is clicked

I am building a Modal dynamically. I want to make some buttons in this modal to open a SubModal. The buttons show up in html, but clicking these buttons does nothing.
Here is my code.
const subtaskList = document.getElementById('subtaskList');
for (const subtaskIndex in task.subtasks) {
const subtaskButton = document.createElement('button');
subtaskButton.classList.add('taskModalSubtaskButton');
subtaskButton.onclick = () => {
openSubTaskModal(task.subtasks[subtaskIndex], task);
}
subtaskButton.innerText = task.subtasks[subtaskIndex].name;
subtaskList.appendChild(subtaskButton);
subtaskList.innerHTML += '<br>';
}
While troubleshooting I made an array to hold the buttons and used console.log() to see its elements. They all had the onclick function. I've clicked the buttons from the dev console by getting their class name and nothing, so I know it's not a display issue. I feel like I am misunderstanding something and any help would be appreciated.
The problem is subtaskList.innerHTML += '<br>'; it is good idea to use subtaskList.appendChild(document.createElement("br")); instead.
Here is working snippet:
function doSomething(url) {
alert(url);
}
const subtaskList = document.getElementById('subtaskList');
for (var i = 0; i < 3; i++) {
const subtaskButton = document.createElement('button');
subtaskButton.classList.add('taskModalSubtaskButton');
subtaskButton.innerText = "name" + i;
subtaskButton.onclick = (function (url) {
return function () {
doSomething(url);
};
})("URL #" + i)
subtaskList.appendChild(subtaskButton);
subtaskList.appendChild(document.createElement("br"));
}
<div id="subtaskList"></div>
Also I change a bit onclick function to send correspond index to doSomething function

how set toggle function for different id

I want different buttons with id and a unique function for toggle, but i can set the variable.
var clicked = false;
var abcElements = document.querySelectorAll('.cellInput');
// Set their ids
for (var i = 0; i < abcElements.length; i++){
abcElements[i].id = 'target-' + i;
$("#target-"+i).click(function () {
if (!clicked) {
// do something
} else {
// do something
}
clicked = !clicked;
})
}
If you want to keep track of the click for each element, you cannot use a shared global variable. You can, however, toggle a class to keep track of state on each element.
$('.cellInput').on('click', function(e){
var $this = $(e.target);
if (!$this.hasClass('clicked')) {
$this.addClass('clicked');
// do something
} else {
$this.removeClass('clicked');
// do something
}
});

How can I remove the first doctype tooltip of the ace-editor in my html-editor?

We ask the user here to define html, so add a div or a section or something like that. So, I want the validation-tooltips when editing my HTML. But don't wanna have the doc-type warning.
Try this
var session = editor.getSession();
session.on("changeAnnotation", function() {
var annotations = session.getAnnotations()||[], i = len = annotations.length;
while (i--) {
if(/doctype first\. Expected/.test(annotations[i].text)) {
annotations.splice(i, 1);
}
}
if(len>annotations.length) {
session.setAnnotations(annotations);
}
});
With "Unexpected End of File. Expected DOCTYPE." warning filtered.
var session = editor.getSession();
session.on("changeAnnotation", function () {
var annotations = session.getAnnotations() || [], i = len = annotations.length;
while (i--) {
if (/doctype first\. Expected/.test(annotations[i].text)) {
annotations.splice(i, 1);
}
else if (/Unexpected End of file\. Expected/.test(annotations[i].text)) {
annotations.splice(i, 1);
}
}
if (len > annotations.length) {
session.setAnnotations(annotations);
}
});
If instead you operate on the annotations directly and call the editor onChangeAnnotation method directly to update the annotations on the page you can prevent firing another changeAnnotation event and calling this event handler twice as Chris's answer does.
var editor = Application.ace.edit(element),
session = editor.getSession();
session.on('changeAnnotation', function () {
session.$annotations = session.$annotations.filter(function(annotation){
return !(/doctype first\. Expected/.test(annotation.text) || /Unexpected End of file\. Expected/.test(annotation.text))
});
editor.$onChangeAnnotation();
});

AddEventListener function won't execute

The iFrameOn function runs on page load, and up until it is supposed to execute the iBold function is works fine. I've gone through and debugged as much as possible, and everything seems fine. When I output every variable to the console, the values are correct. It's just that one line (iBold(targetiFrame);) that won't run. I'm not sure what's going on.
function iFrameOn() {
var iFrames = document.querySelectorAll('form > iframe'); //Get all iframes in forms
var bolds = new Array(), italics = new Array(), underlines = new Array(), targetiFrame;
var getRT = document.getElementsByClassName('richText');
for (var rtIndex = 0; rtIndex < getRT.length;rtIndex++) { //Rich text event listeners
var rtid = getRT[rtIndex].id;
if (getRT[rtIndex].className == "richText bold") { //Bold text event listener
console.log('The id is: '+rtid);
bolds.push(rtid);
console.log('The bolds array contains: '+bolds);
} else if (getRT[rtIndex].className == 'richText underline') { //Underline text event listener
underlines.push(getRT[rtIndex]);
} else if (getRT[rtIndex].className == 'richText italic') { //Italic text event listener
italics.push(getRT[rtIndex]);
}
}
bolds.forEach(function(e, i, a) { //e = a[i]
console.log('e is '+e);
document.getElementById(e).addEventListener('click', function() {
console.log(e+' was clicked!');
targetiFrame = document.getElementById(e).getAttribute('data-pstid');
iBold(targetiFrame);
}, false);
});
}
function iBold(target) {
if (target == 0) {
document.getElementById('richTextField').contentDocument.execCommand('bold', false, null);
document.getElementById('richTextField').contentWindow.focus();
} else {
document.getElementById(target).contentDocument.execCommand('bold', false, null);
document.getElementById(target).contentWindow.focus();
}
}
I apparently had another iBold function in another js file

Show gmaps location when clicking on an item (Gmaps4rails)

Im trying to add some callback to my code, so when clicking on a picture outside the map, the maps get centered to the position that image contains, so I try this.
Gmaps4Rails.callback = function() {
var event = document.getElementsByClassName("link-s");
event.onclick = showEvent;
function showEvent(pos) {
var pos = -25.363882,131.044922;
Gmaps4Rails.map.setCenter(pos.latLng);
};
};
it doesnt throw any error, im using "pos" for testing purposes, but i cant make it work.
It's just a question of javascript:
Gmaps4Rails.callback = function() {
var events = document.getElementsByClassName("link-s");
for (var i = 0; i < events.length; ++i) {
events[i].onclick = function() { showEvent(); };
}
function showEvent() {
Gmaps4Rails.map.setCenter(new google.maps.LatLng(-25.363882,131.044922));
};
}

Categories

Resources