I have written some vanilla JS in order to hide/display some divs, depending on the value of one of the fields in them.
My problem is that, while hiding them appropriately is working by default, trying to make them appear again, I am having issues as using getElementById is returning nulls.
I've googled and found numerous examples where similar code has worked and I cannot figure out why mine isn't actually working.
The JS I've written is below:
var hidden = false
document.addEventListener("keypress", function(event) {
if (event.key == '`') {
if (hidden == false) {
resultEntries = document.getElementsByClassName('result-row');
for (i = 0; i < resultEntries.length + 1; i++) {
var x = document.getElementById('root_cause_' + i)
if (x != null) {
var value = x.options[x.selectedIndex].value;
console.log('value' + value)
if (value == '') {
row = document.getElementById('line_' + i)
row.style.display = 'none';
}
}
}
hidden = true
} else {
resultEntries = document.getElementsByClassName('result-row');
for (i = 0; i < resultEntries.length + 1; i++) {
row = document.getElementById('line_' + i) // This is what is returning null
console.log(row)
row.style.display = 'block';
}
hidden = false
}
}
});
You overshoot your elements with the + 1 in the for loops
If you need this 1 based (not recommended) it is
for (let i = 1; i <= resultEntries.length; i++)
Also I think you can simplify this. Here is my guess without seeing the HTML
const resultEntries = document.querySelectorAll('.result-row');
document.addEventListener("keypress", function(event) {
if (event.key !== '`') return;
resultEntries.forEach((res, i) => {
let x = document.getElementById('root_cause_' + i)
if (x) {
let value = x.value;
console.log('value' + value)
document.getElementById('line_' + i).hidden = value === '';
}
})
});
Answering to save people from spending more time on this:
The issue was that my loop was starting from 0 while the elements were starting from line_1, meaning I was later on trying to alter the style of a null element.
Related
Very new to javascript and can't figure out what I'm doing wrong. Trying to assign the className of a <div> element to a var, and I get this error.
scripts.js:30 Uncaught TypeError: Cannot read property '0' of undefined
at checkWinner (scripts.js:30)
at HTMLDivElement.buttonClick (scripts.js:25)
When I try to figure out if the property even exists, the console is leading me to believe it does. This seems like conflicting info to me.
winLines[0][0].className
"xButt"
Any help is appreciated. I'm sure it's something basic. Here's the code just in case.
var turns = 0;
var gameButtons = Object.values(document.querySelectorAll('.gameButton'));
var winLines= [
[gameButtons[0], gameButtons[1], gameButtons[2]]
/* other arrays go hear */
];
for (let i = 0; i < gameButtons.length; i++) {
gameButtons[i].textContent = null;
gameButtons[i].addEventListener('click', buttonClick);
}
function buttonClick(e) {
console.log(e.target.id + ": You clicked me!");
if (turns % 2 == 0) {
e.target.className = 'xButt';
e.target.textContent = 'X';
e.target.style.backgroundColor = 'green';
} else {
e.target.className = 'oButt';
e.target.textContent = 'O';
e.target.style.backgroundColor = 'blue';
}
turns++;
checkWinner();
}
function checkWinner() {
for (let i = 0; i <= winLines.length; i++) {
let markOne = winLines[i][0].className;
let markTwo = winLines[i][1].className;
let markThree = winLines[i][2].className;
if (markOne === markTwo && markOne === markThree) {
alert("Awww sh********t!");
}
}
}
Your loop has more iterations than your array has elements.
Change the loop like so:
for (let i = 0; i < winLines.length; i++)
The undefined error comes because you're trying winLines[1][0] which doesn't exist because winLines only has one element (at index 0)
I have a MultiSelectDropDown, that is, several RadComboBox controls are used in a combined way. For example, I can have a dropdown for regions, another for depots and another for user. The idea is to change the content of lower levels dynamically whenever items are selected or unselected on a higher level. The problem is that in the case when many items are selected, this becomes brutally slow due to some Telerik functions, but I do not understand why. This is a chunk from the client-side of the MultiSelectDropDown prototype:
changeLowerLevels: function (valueIndex, values, value) {
if (!this.canChange) return;
//Get selected values from combobox
var combo = $find(this.ddlIDs[valueIndex - 1]);
var cbItems = combo.get_checkedItems();
var selectedItems = [];
var change = null;
var counter = 0;
if (cbItems.length) this.filterString = "";
for (var i = 0; i < cbItems.length; i++) {
counter++;
if (this.filterString == "") this.filterString = cbItems[i].get_text();
selectedItems.push(cbItems[i].get_value());
}
if (counter > 1) this.filterString += " with " + (counter - 1) + " other" + ((counter > 2) ? "s" : "");
if (JSON.stringify(selectedItems) === JSON.stringify(this.selectedItems[valueIndex - 1]) || selectedItems == [])
return;
this.selectedItems[valueIndex - 1] = selectedItems;
var controlObject = this;
var combo = $find(this.ddlIDs[valueIndex]);
var comboItems = combo.get_items();
if(!this.disabled) combo.enable();
combo.clearItems();
if (valueIndex == 1) this.twoLevelCache = values;
var val = values;
//break if all items are found
var nrOfSelectedItems = this.selectedItems[valueIndex - 1].length;
var nrOfFoundItems = 0;
var index = 0;
var indexes = [];
var found = false;
while (nrOfFoundItems < nrOfSelectedItems && val[index] !== undefined) {
found = (this.selectedItems[valueIndex - 1].indexOf(val[index].Value) != -1);
if (!(found))
index++;
else {
indexes.push(index)
nrOfFoundItems++;
index++;
}
}
//separators from valuesIndex - 1 level
var controlObject = this;
for (var i = 0; i < indexes.length; i++) {
var separator = new Telerik.Web.UI.RadComboBoxItem();
separator.set_text("<span><a class=\"checkAll tt-multi-uncheck-icon\" index=\"" + index + "\">U</a>" + $find(this.ddlIDs[valueIndex - 1]).findItemByValue(val[indexes[i]].Value).get_text() + "</span>");
separator.set_value("");
separator.set_isSeparator(true);
comboItems.add(separator);
this.twoLevelCache.push(val[indexes[i]].Levels);
//valuesIndex level
var valuesArray = val;
var comboItem = new Telerik.Web.UI.RadComboBoxItem();
for (var depot in valuesArray[indexes[i]].Levels) {
comboItem = new Telerik.Web.UI.RadComboBoxItem();
comboItem.set_text(valuesArray[indexes[i]].Levels[depot].Name);
comboItem.set_value(valuesArray[indexes[i]].Levels[depot].Value);
comboItems.add(comboItem);
comboItem = null;
}
$('#' + this.ddlIDs[valueIndex] + '_DropDown a.checkAll').unbind().on("click", function () {
checkAllLowerItems(this, controlObject.ddlIDs[valueIndex]);
});
}
combo.set_emptyMessage(this.allText);
//$("#" + this.ddlIDs[valueIndex]).html(returnValue);
if (this.ddlIDs.length > valueIndex + 1) {
var paramToPass = (((val == undefined) || (val[index] === undefined)) ? ("") : (val[index]));
if (this.allText.length > 0)
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
else {
if (paramToPass !== "")
paramToPass = paramToPass.Levels;
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.changeLowerLevels(valueIndex + 1, paramToPass, "");
}
else {
this.changeLowerLevels(valueIndex + 1, paramToPass, val[index].Levels[0].Value);
}
}
}
else {
if (this.allText.length > 0)
this.selectedItems[valueIndex] = "";
else
if ((val[index] == undefined) || (val[index].Levels[0] === undefined) || (val[index].Levels[0].Value === "")) {
this.selectedItems[valueIndex] = "";
}
else {
this.selectedItems[valueIndex] = val[index].Levels[0].Value;
}
}
this.setText();
}
combo.clearItems() is extremeley slow. I have take a look on how it is implemented:
function (){var f=this._parent._getControl();?if(f._checkBoxes){f._checkedIndicesJson="[]";?f._checkedIndices=[];?var g=f.get_items();?for(var d=0,e=g.get_count();?d<e;?d++){var c=f.get_items().getItem(d);?c.set_checked(false);?}f.updateClientState();?}a.RadComboBoxItemCollection.callBaseMethod(this,"clear");?}
How can I make sure that this Javascript function speeds up?
I have finally solved the problem by rewriting Telerik client-side functionalities. It was a long and difficult debugging, but it yielded a large performance boost in the most difficult circumstances. From ~30 000 milliseconds, to ~300. Let's see the parts of the optimization:
The actual rewrite
/* Overriding Telerik functions Start */
var overridenTelerikControls = false;
function overrideTelerikFunctionalities() {
if (!overridenTelerikControls) {
overridenTelerikControls = true;
Telerik.Web.UI.RadComboBox.prototype.clearItems = function (isMultiSelectDropDown) {
this.get_items().clear(isMultiSelectDropDown);
this._itemData = null;
};
Telerik.Web.UI.RadComboBoxItemCollection.prototype.clear = function (isMultiSelectDropDown){
var f=this._parent._getControl();
if(f._checkBoxes){
f._checkedIndicesJson="[]";
f._checkedIndices=[];
var g = f.get_items();
for(var d=0,e=g.get_count();d<e;d++){
var c=f.get_items().getItem(d);
c.set_checked(false, isMultiSelectDropDown);
}
if (isMultiSelectDropDown) {
f._updateComboBoxText();
if (f._checkAllCheckBoxElement != null) {
f._updateCheckAllState();
}
}
f.updateClientState();
}
Telerik.Web.UI.RadComboBoxItemCollection.callBaseMethod(this, "clear");
};
Telerik.Web.UI.RadComboBoxItem.prototype.set_checked = function (d, isMultiSelectDropDown){
if(!this.get_enabled()){
return;
}
this._setChecked(d);
var c=this.get_comboBox();
if(c){
if(d){
c._registerCheckedIndex(this.get_index());
}else{
c._unregisterCheckedIndex(this.get_index());
}
if (!isMultiSelectDropDown) {
c._updateComboBoxText();
}
if((!isMultiSelectDropDown) && (c._checkAllCheckBoxElement!=null)){
c._updateCheckAllState();
}
}
};
}
}
/* Overriding Telerik functions End*/
My approach was to keep the old way of their working by default, but if an isMultiSelectDropDown parameter is passed, then work in the optimized manners. So we have a switch materialized as a parameter and we can turn it on/off. The main difference was that the old way was to change the label text showing the selected elements each time a checkbox is checked/unchecked. The main improvement was to do this change after all the checkboxes were checked/unchecked. This extremely simple idea was the driving force behind the boost of performance.
Actual usage
overrideTelerikFunctionalities();
combo.clearItems(true);
This was the functionalities were overriden if they were not already and the parameter was true, therefore the new approach was chosen.
Test, test, test
I want to create an unordered list. The order should be given by their id (Which is a number. The smallest is at the bottom.)
BUT if a certain li does not have a a CSS property (text-decoration:line-through in my case.) Then they should be at the bottom anyway.
I am trying to make a To Do list, where checked elements are at the bottom, but when you uncheck them, they jump back to place.
http://codepen.io/balazsorban44/pen/Gjzwbp
const inputArea = document.getElementById('todo-input');
const list = document.getElementById('tasks');
tasks = [];
function loaded() {
inputArea.focus();
}
function enter() {
if (event.keyCode == 13) {
addTask()
}
}
function refresh(array) {
var task = document.createElement('li');
const checkBox = document.createElement('input');
checkBox.setAttribute('type', 'checkbox');
checkBox.setAttribute('onclick', 'toggleCheckBox()');
task.appendChild(checkBox);
task.appendChild(document.createTextNode(array[array.length - 1].task));
task.id = array[array.length - 1].timestamp.getTime()
list.appendChild(task);
return list;
}
function addTask() {
if (inputArea.value != '') {
tasks.push({
timestamp: new Date(),
task: inputArea.value
});
inputArea.value = '';
inputArea.focus();
refresh(tasks);
doneOrUndone();
inputArea.placeholder = 'Click + or press Enter.'
} else {
inputArea.placeholder = 'Write something'
}
}
function doneOrUndone() {
var done = 0
const out = document.getElementById('out')
for (var i = 0; i < tasks.length; i++) {
if (document.getElementsByTagName('li')[i].style.textDecoration != '') {
done++
}
}
out.value = parseInt(done) + '/' + parseInt(tasks.length) + ':'
}
function toggleCheckBox() {
const task = event.target.parentNode
if (task.style.textDecoration == '') {
task.style.textDecoration = 'line-through';
list.appendChild(task)
doneOrUndone()
} else {
task.style.textDecoration = ''
for (var i = 0; i < tasks.length; i++) {
if (task.id < tasks[i].timestamp.getTime() && list.childNodes[0].style.textDecoration == 'line-through') {
if (task.id > list.childNodes[0].id) {
list.insertBefore(task, list.childNodes[0]);
break;
}
} else {
list.insertBefore(task, list.childNodes[i]);
break
}
}
doneOrUndone()
}
}
I think the real problem is that when setting style.textDecoration to line-through, the actual property that is set is textDecorationLine.
Your line if (task.style.textDecoration == '') is therefore always true.
You can replace textDecoration with textDecorationLine and it should work.
http://codepen.io/anon/pen/bwzzkP
The underlying cause of this problem is, in my opinion, that you're using an element's style attribute to monitor state. I'd advice to either:
Store state in javascript, or
Store state in a class, or
Store state in a custom data- attribute
For example:
const task = event.target.parentNode;
const isDone = task.getAttribute("data-done");
task.style.textDecoration = !isDone ? 'line-through' : '';
task.setAttribute("data-done", !isDone);
I did some code to change my image position
This code can run once , but doesn't work second time.
function prev() {
if(document.getElementById("bookCon").style.left=="0px"){}
else {
document.getElementById("bookCon").style.right-="800px";
}
}
function next() {
if(document.getElementById("bookCon").style.left=="5000px") {}
else {
document.getElementById("bookCon").style.right+="800px";
}
}
second edition ... also not work.
function getStyleNum(item,prop) {
if (typeof item == "string") { item = document.getElementById(item); }
return parseInt(item.style[prop],10);
}
function prev() {
var item = document.getElementById("bookCon");
if (getStyleNum(item,"right") !== 0) {
var val = getStyleNum(item, "right");
item.style.right = (val - 800) + "px";
}
}
function next() {
var item = document.getElementById("bookCon");
if (getStyleNum(item,"right") !== 1600) {
var val = getStyleNum(item, "right");
item.style.right = (val + 800) + "px";
}
}
There are multiple issues with your code:
You can't do += "800px" because you will end up with something like "500px800px" as these are strings.
You can't do math on strings.
You can't reliably compare to "0px"
You check the value of .left and set the value of .right. You should only be using one or the other, not both.
Your code could a lot more DRY (not repeating things)
Reading the style property directly on the object won't include anything set via style sheets
If the style.left value is directly set on the object (and not initially coming from a style sheet) and your object is positioned, then you can use this type of logic (converting to numbers). I don't know exactly what you're trying to accomplish by checking .left and then changing .right so this is just a guess at your logic, but you should be able to see the general idea of how you do this sort of thing.
function prev() {
var item = document.getElementById("bookCon");
if (parseInt(item.style.left, 10) != 0) {
var val = parseInt(item.style.right, 10) || 0;
item.style.right = (val - 800) + "px";
}
}
You may find this util function useful:
function getStyleNum(item, prop) {
if (typeof item == "string") {
item = document.getElementById(item);
}
return parseInt(item.style[prop], 10) || 0;
}
function prev() {
var item = document.getElementById("bookCon");
if (getStyleNum(item, "left") != 0) {
var val = getStyleNum(item, "right");
item.style.right = (val - 800) + "px";
}
}
P.S. I still don't understand why you would check the value of .left and then modify the value of .right. Only one of these can be active at a given time.
Working demo: http://jsfiddle.net/jfriend00/awjv9/
I am currently having some issues with the innerHTML function in a little javascript project. Essentially, I have a few HTML form checkboxes which change a number (that is displayed on the same page) depending on whether they are checked or not. The idea is very much like an IP address. The result is a number between 0 and 255.
What I want to do however is that whenever the user clicks on a checkbox, I need that number to change dynamically. Idea resembles the concept that is used when we write a question on this forum. As you type, the text below changes dynamilly to show exactly what is changed as it changes.
My code isn't working too well. Could you help me please? It keeps giving me the message "undefined" instead of the sum. Thanks for your help.
JavaScript
function displayOctets01(){
var octet01 = new Array(8);
octet01[0] = document.getElementById('octect0101');
octet01[1] = document.getElementById('octect0102');
octet01[2] = document.getElementById('octect0103');
octet01[3] = document.getElementById('octect0104');
octet01[4] = document.getElementById('octect0105');
octet01[5] = document.getElementById('octect0106');
octet01[6] = document.getElementById('octect0107');
octet01[7] = document.getElementById('octect0108');
var firstOctect;
if(octet01[0]==true){
firstOctect+=1;
}
else if(octet01[1]==true){
firstOctect+=2;
}
else if(octet01[2]==true){
firstOctect+=4;
}
else if(octet01[3]==true){
firstOctect+=8;
}
else if(octet01[4]==true){
firstOctect+=16;
}
else if(octet01[5]==true){
firstOctect+=32;
}
else if(octet01[6]==true){
firstOctect+=64;
}
else if(octet01[7]==true){
firstOctect+=128;
}
document.getElementById("octets01").innerHTML = firstOctect;
}
else if(octet01[7]==true){
firstOctect+=128;
}
document.getElementById("octets01").innerHTML = firstOctect;
}
I suspect that something might be wron with how I am handling the variables.
DEMO: http://jsfiddle.net/3TyV3/
The first problem is that the firstOctet variable isn't initialized. That needs to be set to 0 at the beginning of your function. Also, without knowing the purpose of your program, it seems that you don't want to be using else if - you need to check every checkbox. Also, you shouldn't be comparing the element with == true, you should check its checked property Also, your jsFiddle was set to run onLoad, so the function wasn't globally available. Finally, you didn't have an element with the id "octets01" to output to. Try this:
function displayOctets01() {
var octet01 = [],
firstOctect = 0;
octet01[0] = document.getElementById('octect0101');
octet01[1] = document.getElementById('octect0102');
octet01[2] = document.getElementById('octect0103');
octet01[3] = document.getElementById('octect0104');
octet01[4] = document.getElementById('octect0105');
octet01[5] = document.getElementById('octect0106');
octet01[6] = document.getElementById('octect0107');
octet01[7] = document.getElementById('octect0108');
if (octet01[0].checked === true) {
firstOctect += 1;
}
if (octet01[1].checked === true) {
firstOctect += 2;
}
if (octet01[2].checked === true) {
firstOctect += 4;
}
if (octet01[3].checked === true) {
firstOctect += 8;
}
if (octet01[4].checked === true) {
firstOctect += 16;
}
if (octet01[5].checked === true) {
firstOctect += 32;
}
if (octet01[6].checked === true) {
firstOctect += 64;
}
if (octet01[7].checked === true) {
firstOctect += 128;
}
document.getElementById("octets01").innerHTML = firstOctect;
}
DEMO: http://jsfiddle.net/3TyV3/2/
Although I won't lie, I'd reorganize some things. Here's how I would do it:
window.onload = function () {
var checkboxes = document.querySelectorAll('[name="featuresOctet01"]'),
i;
for (i = 0; i < checkboxes.length; i++) {
addEvent(checkboxes[i], "click", clickHandler);
}
};
function addEvent(element, eventName, callback) {
if (element.addEventListener) {
element.addEventListener(eventName, callback, false);
} else if (element.attachEvent) {
element.attachEvent("on" + eventName, callback);
} else {
element["on" + eventName] = callback;
}
}
function clickHandler() {
var firstOctect = 0,
checkboxes = document.querySelectorAll('[name="featuresOctet01"]'),
i, cur;
for (i = 0; i < checkboxes.length; i++) {
cur = checkboxes[i];
if (cur.checked) {
firstOctect += Math.pow(2, i);
}
}
document.getElementById("octets01").innerHTML = firstOctect;
}
DEMO: http://jsfiddle.net/3TyV3/3/
It uses unobtrusive JavaScript by binding the events in JavaScript, not the inline HTML. I did use the click event instead of change because old versions of IE has weird behavior for it with checkboxes/radio buttons. The addEvent function is just a simple function for binding events in new browsers as well as old IE.
It selects all elements with the name "featuresOctet01" and adds the event to each. Then, in the handler, it loops through each checkbox, sees if it's checked, and then adds a value based on 2^i.
References:
addEventListener: https://developer.mozilla.org/en-US/docs/DOM/EventTarget.addEventListener
document.querySelectorAll: https://developer.mozilla.org/en-US/docs/DOM/Document.querySelectorAll
Math.pow: https://developer.mozilla.org/en-US/docs/JavaScript/Reference/Global_Objects/Math/pow