I have a page that sorts things if you choose one of the options. The list of filters was too long so I added a div that shows/hides options. On page load some of the options are hidden via CSS with display: hidden. Code below just changes display from display: none to display: inline-block based on number of clicks.
var timesClicked = 0;
window.onload = function(){
document.getElementById("filter_attribute_217").onclick = function(){
timesClicked++;
if (timesClicked%2==0){
hide();
} else {
show();
}
};
This is show() function
function show(){
document.getElementById("filter_attribute_69").style.display="inline-block";
document.getElementById("filter_attribute_70").style.display="inline-block";
document.getElementById("filter_attribute_72").style.display="inline-block";
//all those other options
document.getElementById("filter_attribute_217").innerHTML = "less";
document.getElementById("filter_attribute_217").style.borderTop = "1px dashed #e1e4e6";
document.getElementById("filter_attribute_68").style.borderBottom = "none";
document.getElementById("filter_attribute_87").style.borderBottom = "none";
}
And this is hide() function
function hide(){
document.getElementById("filter_attribute_217").innerHTML = "more";
document.getElementById("filter_attribute_217").style.borderTop = "1px dashed #e1e4e6"
document.getElementById("filter_attribute_69").style.display="none";
document.getElementById("filter_attribute_70").style.display="none";
//all those other options
document.getElementById("filter_attribute_103").style.display="none";
}
This is working perfectly until I choose one of the options which means that some other disappear. Then when trying to use more/less button I will get errors like this one:
Uncaught TypeError: Cannot read properties of null (reading 'style')
at hide (user.js?sci=481:68:50)
at document.getElementById.onclick (user.js?sci=481:58:9)
Clicking it again shows the same error just with show() function.
I've tried checking if element exist in DOM structure with document.body.contains(YOUR_ELEMENT_HERE); but it always shows that all of the options exists. Checked it with
if(document.body.contains(element){
console.log("exists")
} else {
console.log("doesn't exist")
}
Console was always outputting "exists".
I then tried just ignoring the problem to see if others options will show with
function stoperror() {
return true;
};
window.onerror = stoperror;
But with the same result just without the error. I don't know why this is happening and how can I fix it. I need to add that all I can edit here is JavaScript and CSS. That's why I made another option (filter_attribute_217) and made it into my show more/less button.
Edit
Here's HTML code for basically every option. They only thing that is changing from option to option is id and href.
<div data-limit="3" class="group group-filter" id="filter_attribute_104">
<h5>
Transparent
</h5>
<ul>
<li class="" style="margin-right:15px;">
<a title="tak" href="/pl/c/Balls/119/1/default/1/f_at_104_0/1">
<i src="/libraries/images/1px.gif" alt="" class="px1" ></i>
<span>tak </span>
<em>(56)</em>
</a>
</li>
</ul>
<div data-limit="3" class="group group-filter" id="filter_attribute_68">
<h5>
White
</h5>
<ul>
<li class="" style="margin-right:15px;">
<a title="tak" href="/pl/c/Balls/119/1/default/1/f_at_68_0/1">
<i src="/libraries/images/1px.gif" alt="" class="px1" ></i>
<span>tak </span>
<em>(208)</em>
</a>
</li>
</ul>
I also have this in my JS code
window.addEventListener("DOMContentLoaded",(event) => {
document.getElementById("filter_attribute_217").innerHTML = "więcej";
document.getElementById("filter_attribute_217").style.borderTop = "1px dashed #e1e4e6";
});
This is an example with the code you provided. However your question is still not properly stated. I assume the HTML is getting generated dynamically? If so, I would make sure that the elements I'm trying to use are existing for sure while I call my JS. So I got rid of the window.onload you are using. Also, if you want to apply global changes, do not repeat the same things for each ID, just use a querySelectorAll('.classname') with a .forEach. Example:
let timesClicked = 0;
let show = () => {
document.querySelector('.group-filter').innerHTML = 'LESS';
document.querySelectorAll('.group-filter').forEach(elem => {
elem.style.display = 'flex';
// Other things...
});
}
let hide = () => {
document.querySelector('.group-filter').innerHTML = 'MORE';
document.querySelectorAll('.group-filter').forEach(elem => {
elem.style.display = 'none';
// Other things...
});
// Restore the first element's display
document.querySelector('.group-filter').style.display = 'flex';
}
document.getElementById('filter_attribute_104').addEventListener('click', () => {
timesClicked++;
if (timesClicked % 2 == 0) hide();
else show();
});
.hidden {display:none;}
<div data-limit="3" class="group group-filter" id="filter_attribute_104">
<h5>Transparent</h5>
<ul>
<li class="" style="margin-right:15px;">
<a title="tak" href="#">
<i src="/libraries/images/1px.gif" alt="" class="px1" ></i>
<span>tak </span>
<em>(56)</em>
</a>
</li>
</ul>
</div>
<div data-limit="3" class="group group-filter hidden" id="filter_attribute_68">
<h5>White</h5>
<ul>
<li class="" style="margin-right:15px;">
<a title="tak" href="#">
<i src="/libraries/images/1px.gif" alt="" class="px1" ></i>
<span>tak </span>
<em>(208)</em>
</a>
</li>
</ul>
</div>
<div data-limit="3" class="group group-filter hidden" id="filter_attribute_168">
<h5>Red</h5>
<ul>
<li class="" style="margin-right:15px;">
<a title="tak" href="#">
<i src="/libraries/images/1px.gif" alt="" class="px1" ></i>
<span>tak </span>
<em>(332)</em>
</a>
</li>
</ul>
</div>
Related
Goal: Change style of an element in a forEach loop
Issue: Style not being applied
In my loop, if I console.log(element), I do get the right list of filtered elements.
Running this works, but I want my function to modify every element that match my filter:
let contentPreview = document.querySelectorAll('.o_last_message_preview');
contentPreview[0].style.color = 'yellow'
All elements are correctly assigned.
function changeNotificationColor() {
// Notification button that opens list of notifications
let notifyButton = document.querySelector('.dropdown-toggle.o-no-caret[title="Conversations"]');
notifyButton.addEventListener('click', function () {
// List of notifications - each notification is contained in a .o_last_message_preview class
let contentPreview = document.querySelectorAll('.o_last_message_preview');
contentPreview.forEach(function (element) { if (element.innerText.includes('Marine')) {element.style.color = 'yellow'}})
})
}
changeNotificationColor();
HTML Notifications Dropdown button:
<a class="dropdown-toggle o-no-caret" data-toggle="dropdown" data-display="static" aria-expanded="true" title="Conversations" href="#" role="button">
<i class="o_mail_messaging_menu_icon fa fa-comments" role="img" aria-label="Messages"></i> <span class="o_notification_counter badge badge-pill">366</span>
</a>
HTML template of a notification node (each notification creates one of these nodes):
<div class="o_preview_info">
<div class="o_preview_title">
<span class="o_preview_name">
You have been assigned to Backorder xx
</span>
<span class="o_preview_counter">
(1)
</span>
<span class="o_last_message_date ml-auto mr-2"> 3 minutes ago </span>
</div>
<div class="o_last_message_preview">
Marine
</div>
<span title="Mark as Read" class="o_discuss_icon o_mail_preview_mark_as_read fa fa-check"></span>
</div>
I don't know what your issue is, but it's not in the code that you showed; this works fine:
let contentPreview = document.querySelectorAll('.o_last_message_preview');
contentPreview.forEach(function (element) { if (element.innerText.includes('Marine')) {element.style.color = 'yellow'}})
<div class="o_last_message_preview">This contains Marine</div>
<div class="o_last_message_preview">This does not</div>
<div class="o_other_message_preview">This has a different class</div>
I have attached the java Script file in body section of the page but its still not working.
Here is #javascript function that i want to call
function opentab(evt, tab) {
var i, content, links;
content = document.getElementsByClassName("main");
for(i = 0;i < content.length;i++){
content[i].display="none";
}
links=document.getElementsByClassName("linked");
for(i=0; i < links.length;i++){
links[i].className=links[i].className.replace("active", "");
}
window.document.getElementById(tab).style.display = "block";
evt.currentTarget.className += " active";
}
This is the #HTML code i want to call the above method onclick of <a> tag to show the specific div.
<div class="sidebar">
<img src="PNG%20WE%20Carry%20Fast-01.png" alt="Logo">
<a href="#" onclick='opentab(event, 'home')' class="linked" ><i class="fas fa-home" ></i>
Home </a>
<a href="#" onclick='opentab(event, 'routes')' class="linked" >
<i class="fas fa-route"></i> Routes</a>
<a href="#" onclick='opentab(event, 'vehicles')' class="linked" ><i class="fas fa-truck-moving"></i>
Vehicles</a>
</i> Qr Code
</i> Order Status
<a href="#" onclick='opentab(event, 'Report')' class="linked" ><i class="fas fa-chart-bar"></i>
Report</a>
</div>
<div id="home" class="main">
<h1>Hi welcome to your home page
</h1>
</div>
onclick='opentab(event, 'OrderStatus')' would not execute because of incorrectly nested quotes.
Try onclick="opentab(event, 'OrderStatus')"
so that the inside quotes '' are not in conflict with the outside double quotes ""
Also there is no display property associated with a division. What you are trying to achieve is to set the style property.
content[i].style.display = "none";
I have a MaterializeCSS dropdown menu implemented with this HTML. The event listener works only if the dropdown item is clicked in the upper portion.
<div class="left">
<span id="notificationTotal" class="new badge red" style="display:none">
</span>
<a class="dropdown-trigger" href="#!" data-target="notificationsDropdown">
<i class="material-icons"> message</i>
</a>
</div>
<ul id="notificationsDropdown" class="dropdown-content">
</ul>
I'm using the following Javascript to populate the menu with notifications. This is working just fine.
// Reset Notification Dropdown
notificationsDropdown.innerHTML = '';
notifications.forEach(notif => {
const displayTime = moment(notif.date).fromNow();
let typeIcon = 'sms';
if (notif.type === 'chat') {
typeIcon = 'lock';
}
notificationsDropdown.innerHTML += `<li class="notification">
<a style="margin-top:0px;margin-bottom:0px;padding-bottom: 0;font-size:14px" href="#" class="blue-text">
<span class="js-patientName">
${notif.contact.firstName} ${notif.contact.lastName}</span>
<span class="js-notificationPhone" style="display:none">${
notif.contact.phone
}</span>
<span class="js-patientId" style="display:none">${
notif.patientId
}</span>
<span class="js-patientDOB" style="display:none">${
notif.contact.birthDate
}</span>
<p style="margin-top:0px;margin-bottom:0px;padding-bottom: 0;padding-top: 0;">
<i style="display: inline-flex; vertical-align:middle" class="tiny material-icons">${typeIcon}</i>
<span class="black-text" style="font-size:12px">
${displayTime}
</span>
</p>
</a></li>`;
});
notificationsDropdown.innerHTML += `<li class="divider" class="blue-text"></li><li>See All Notifications</li>`;
}
The dropdown gets populated and when a user clicks on a particular dropdown .li entry depending on the exact location they click, it may or not work. The user must click at the main top of the dropdown item.
This is the event listener code that extracts the values from the hidden span elements.
document
.querySelectorAll('#notificationsDropdown', '.li .a .notification')
.forEach(input =>
input.addEventListener('click', async e => {
// console.log('clicked', e.target);
console.log(e.target.parentNode);
const name = e.target.children[0].textContent.trim();
const phone = e.target.children[1].textContent.trim();
const patientId = e.target.children[2].textContent.trim();
const birthDate = e.target.children[3].textContent.trim();
console.log('patientid ', patientId);
const patient = {
name,
patientId,
phone,
birthDate
};
Is there a way I can rewrite the eventListener code to resolve this issue? Possibly instead of using e.target.children[insert_number_here].textContent I could use .closest('js-patientId') or similar ?
This is how the HTML is rendered to the page. This is an example of a single notification:
<ul
id="notificationsDropdown"
class="dropdown-content"
tabindex="0"
style="display: block; width: 177.297px; left: 1648.7px; top: 0px; height: 251px; transform-origin: 100% 0px; opacity: 1; transform: scaleX(1) scaleY(1);"
>
<li class="notification">
<a
style="margin-top:0px;margin-bottom:0px;padding-bottom: 0;font-size:14px"
href="#"
class="blue-text"
>
<span class="js-patientName">ANDREW TAYLOR</span>
<span class="js-notificationPhone" style="display:none">
5555551212
</span>
<span class="js-patientId" style="display:none">
1
</span>
<span class="js-patientDOB" style="display:none">
1960-01-01
</span>
<p style="margin-top:0px;margin-bottom:0px;padding-bottom: 0;padding-top: 0;">
<i
style="display: inline-flex; vertical-align:middle"
class="tiny material-icons"
>
sms
</i>
<span class="black-text" style="font-size:12px">
2 hours ago
</span>
</p>
</a>
</li>
<li class="divider" />
<li>
<a href="/notifications" class="blue-text">
See All Notifications
</a>
</li>
</ul>;
For dropdowns, the better way to handle actions is through onChange event. So you could attach your eventHandler on onChange of dropDown itself rather than attaching onClick on each of dropDown items. So whenever you change the selected value the onChange event will be triggered and you can easily get the selected value.
I was able to remove the in each li. Then adjust the value assignment to use the following:
e.target.parentNode.children[0].textContent.trim();
I have an ng-repeat which has a button that has a function that toggles an ng-show element inside that ng-repeat.
The inside the class movie_option span has an ng-click=toggleInfo($index):
And the div additional_info has an ng-show that shows or hides an element.
<ul ng-cloak="showResult">
<li class="search_results" ng-repeat="movie in movies | orderBy: '-release_date' track by $index">
<div class="movie_info">
<div class="movie_options">
<div class="slide">
<span class="movie_option">
<span><i class="fa fa-question-circle" aria-hidden="true" ng-click="toggleInfo($index)"></i></span>
</span>
</div>
</div>
</div>
<div class="additional_info" ng-show="hiddenDiv[$index]">
{{movie.overview}}
</div>
</li>
</ul>
When a user clicks on the icon it calls this function:
$scope.toggleInfo = function (index) {
$scope.hiddenDiv[index] = !$scope.hiddenDiv[index];
}
This toggles the ng-show state from the hiddenDiv ng-show. This works fine.
What I wanted to do is put all hiddenDiv states on false except the one that is clicked so only one ng-show would be true.
That's a pure algorithm problem, not related to Angular.
Instead of having a boolean per item, it would be much simpler to just remember the element (index) that should be displayed:
<ul ng-cloak="showResult">
<li class="search_results" ng-repeat="movie in movies | orderBy: '-release_date' track by $index">
<div class="movie_info">
<div class="movie_options">
<div class="slide">
<span class="movie_option">
<span><i class="fa fa-question-circle" aria-hidden="true" ng-click="model.displayedIndex = $index"></i></span>
</span>
</div>
</div>
</div>
<div class="additional_info" ng-show="$index === model.displayedIndex">
{{movie.overview}}
</div>
</li>
</ul>
And in your controller $scope.model = {}
Fiddle: http://jsfiddle.net/6dkLqgfL/
I think this would do:
$scope.toggleInfo = function(index) {
for(var i in $scope.hiddenDiv) {
if(index != i)
$scope.hiddenDiv[i] = false;
}
$scope.hiddenDiv[index] = !$scope.hiddenDiv[index];
}
You could just manually turn all the hiddenDiv elements to false when the toggleInfo function is fired.
$scope.toggleInfo = function(index){
for(int i = 0; i<($scope.hiddenDiv).length; i++)
$scope.hiddenDiv[i] = false;
$scope.hiddenDiv[index] = !$scope.hiddenDiv[index];
}
See if this works?
I have a list that when you click on an li, it reveals a hidden list of information about that person.
In the footer, theres simple navigation showing the different views and when you click, angular filters through the original list for the matched elements.
All I am stuck on is this;
if you click an li element and reveal the info for that person, then click one of the navigation buttons, it will still show that person but with the hidden element revealed...not closed.
Ideally, id prefer that when the user clicks any of the footer navigation buttons, the list reveals just the names, not the hidden info..regardless of whether it was clicked or not.
If this was just in Jquery or javascript, i would know how to approach this but, Im sure theres an 'angular specific' approach I just don't know about.
Heres the HTML:
<div ng-controller="MyCtrl">
<ul id="housewrapper" ng-cloak>
<li ng-repeat="item in house track by item.member" class="listings" ng-click="showComments = !showComments;" ng-show="([item] | filter:filters).length > 0" >
<span ng-if="item.whereHesheStands == 'oppose'"><img class="dot againstdot" src="img/against.png">{{item.member}}
</span>
<span ng-if="item.whereHesheStands == 'leanoppose'">
<img class="dot againstdot" src="img/against.png">{{item.member}}
</span>
<span ng-if="item.whereHesheStands == 'support'" ng-click="clickMeImg($event);">
<img class="dot supportdot" src="img/support.png">{{item.member}}
</span>
<span ng-if="item.whereHesheStands == 'leansupport' ">
<img class="dot supportdot" src="img/support.png">{{item.member}}
</span>
<span ng-if="item.whereHesheStands == 'unknown' ">
<img class="dot undecideddot" src="img/undecided.png">{{item.member}}
</span>
<span ng-if="item.whereHesheStands == 'undecided' ">
<img class="dot undecideddot" src="img/undecided.png">{{item.member}}
</span>
<div class="memberdetail" ng-show="showComments" ng-click="$event.stopPropagation();" >
<ul class="memberbreakdown">
<li class="partyline" >
{{item.party}} - {{item.state}}</li>
<li class="comments">
<span style="color:#a4a4a4;" ng-if="!(item.comments)">Comment not available</span>
<span>{{item.comments}}</span>
</li>
</ul>
</div>
</li>
</ul>
<div id="appfooterWrapper">
<ul id="appfooter">
<li ng-click="myFunctionRepublican();" ng-class="class">R</li>
<li ng-click="myFunctionDemocrat();" ng-class="class2">D</li>
<li ng-click="myFunctionSupport();" ng-class="class3">S</li>
<li ng-click="myFunctionOppose();" ng-class="class4">A</li>
<li ng-click="myFunctionUnknown();" ng-class="class5">U</li>
</ul>
</div>
and the javascript of the "R" navigation button
$scope.myFunctionRepublican = function() {
$('.memberdetail').removeClass('ng-show');
$('.memberdetail').bind('click');
$scope.filters = function(house) {
return house.party == 'R' ;
};
if ($scope.class === ""){
$scope.class = "rep";
$scope.class2 = "";
$scope.class3 = "";
$scope.class4 = "";
$scope.class5 = "";
}
else{
$scope.class = "";
}
$('html, body').animate({scrollTop:0}, 'fast');
var loading;
loading = true;
if (loading == true) {
setTimeout(function() {
spinner.stop();
$('.listings').not('.ng-hide').addClass('republican');
console.log($('.republican').length);
$('#housewrapper').stop().fadeIn(350).addClass(
'marginAdd');
$('#subhead').removeClass('slidedown');
$('#subhead').html('Republicans').css('color', '#d41600');
setTimeout(function() {
$('#subhead').addClass('slidedown');
}, 300);
}, 500);
}
}
Here's the Fiddle
A couple changes, attach a property onto each member.
View changes:
ng-click="item.showComments = !item.showComments;"
<div class="memberdetail" ng-show="item.showComments" ng-click="$event.stopPropagation();" >
Controller changes:
function resetShow() {
for(var i = 0, l = $scope.house.length; i < l; i++) {
$scope.house[i].showComments = false;
}
}
Then just call it when you navigate:
$scope.myFunctionUnknown = function() {
resetShow();
....
Forked Fiddle