Change CSS of Dynamic List Item - javascript

===UPDATE===
If I remove the style="display: none; from the template and apply the below method as recommended below, the empty container fires when you click on any other list item. What else can be done?
I have an ul list that is dynamically created at run time using jQuery and JSON (Using inline HTML is a template). I need the background CSS style to change when a user clicks on a list item (#navItem). I've tried everything under the moon that I can think of from inline class to .appentTo(), etc. What I have below works fine for hard-coded elements but Nothing seems to work with dynamically loaded content. Whats even more confusing is that the classes in the elements within the li tag initiate...???
Any help would be appreciated. Below are my code snippets. Thnx.
HTML:
<div id="navScrollContainer" class="navContentPosition">
<ul id="navContent">
// Display as 'None' to prevent a empty containter from showing -->
<li id="navItem" class="ulFx" style="display: none;">//<-THIS NEEDS TO CHANGE ONCLICK!!
<a class="navA">
<h1 class="navH1">.</h1>
<h2 class="navH2">.</h2>
<p class="navP">.</p>
<hr class="navHR" />
</li>
</ul>
</div>
<script type="text/javascript">
$('#navScrollContainer').on('click', '.ulFx', function() {
$(this).addClass("liFx");
});
</script>
This is the Function that injects the data into the DOM as a list:
function loadNav(url, container, appendE) {
$.getJSON(url, function(data) {
$.each(data.items, function() {
var newItem = $('#' + container).clone();
// Now fill in the fields with the data
newItem.addClass('ulFx');
newItem.find("h1").text(this.label);
newItem.find("h2").text(this.title);
newItem.find("p").text(this.description);
newItem.find("a").attr("href", this.gotoURL);
newItem.children().appendTo('#' + appendE);
});
$('#navHeaderTitle').text(data.listTitle);
iniScroll('scrollNav', 'navScrollContainer');
var target = data.targetKey;
// transition("#" + pageName, "show");
});
};
The CSS that need to happen (only on that item) when the user clicks on a Item:
#-webkit-keyframes
liBG {from {
background-color: transparent
}
50% { background-color: rgba(51,102,255,0.15); }
to {
background-color: transparent
}
}
.liFx {
-webkit-animation-name: liBG;
-webkit-animation-duration: 1s;
-webkit-animation-fill-mode: forwards;
}
The Class atributes given to the li items:
.navH1 {
font-size: 18px;
color: #FFA500;
text-decoration: underline;
font-weight: bold;
margin-top: 8px;
margin-bottom: 8px;
margin-left: 15px;
}
.navH2 {
font-size: 16px;
color: #999999;
font-weight: bold;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 25px;
font-weight: bold;
}
.navP {
color: #888;
font-size: 14px;
text-align: justify;
margin-top: 5px;
margin-bottom: 16px;
margin-left: 25px;
margin-right: 10px;
}
.navA {
text-decoration: none;
}
.navHR {
border: none;
background-color: #336;
height: 1px;
}

This will watch for dynamic elements:
$(".liFx").live("click", function() {
$(this).addClass("liBG");
});

Without seeing your click handler, I can only speculate. However, generally when the problem is related to dynamic content and having them respond to stimulus, the problem lies in how you are attaching the handler.
If you use .click(), or .trigger('click'), the handler will be applied directly to the elements you are calling these functions on. That means that if the elements do not currently exist, they will not receive a handler.
The way to get around this, is to attach the event listener to a parent element that will always exist and then watch for the event propagating up from the dynamic child element. You could do this manually, by looking at the event.target, but jQuery, as usual, makes this easy for us.
The modern jQuery way of doing this is using .on() (documentation):
$('#someparent').on('click', '#childselector', function() {
// my handler code
});
jQuery then attaches a handler on #someparent, and when it sees a click that was targeted at #childselector, it fires.
If you want to apply a class to a child of #navContent, and #navContent will always exist, do this:
$('#navContent').on('click', 'li', function() {
$(this).addClass("liFx");
});
If #navContent is dynamic too, simply go higher in the DOM tree.
As a side note, I notice that the li has an id of navItem. This sounds an awful lot like a class, rather than an ID. If you are going to have more than one navItem, they cannot all have the same ID. This is what classes are for:
<li class="navItem liFx" style="display: none;">

I am not sure where is the problem, but you are trying to do something as such:
$("#navlink").on('click', function() {
$("#yourselector").css("backgroundColor", "#ddd"); //change the color
});

I added another div and an addClass() method to my function along with Jeff B's answer above. If the class is hard coded into the tag, it doesnt function.
<ul id="navContent">
<li id="navItem" style="display: none;">
<div>//ADDED THIS TAG TO EXCEPT THE CLASS
<a>
<h1>.</h1>
<h2>.</h2>
<p>.</p>
<hr/>
</a>
</div>
</li>
</ul>
In my js file:
$.each(data.items, function() {
var newItem = $('#' + container).clone();
// Now fill in the fields with the data
newItem.find("div").addClass("ulFx");//ADDED THIS METHOD
newItem.find("h1").text(this.label);
newItem.find("h2").text(this.title);
newItem.find("p").text(this.description);
newItem.find("a").attr("href", this.gotoURL);
newItem.children().appendTo('#' + appendE);
});

Related

Javascript dynamic content event delegation problem

Every time when a button is clicked, a checkmark will be displayed. If the clicked button is not active, the checkmark is removed. Also when the button is clicked the "load dynamic content"-button will be displayed. There is a problem when dynamic content is loaded. After loading, checkmarks appear.But removing a checkmark does not work. I use a nested event listener for dynamic content. When I check console log after loading, button class elements has not changed. I cannot figure out how to solve this. Any advice?
const checkMarks = document.querySelectorAll('.ck');
// Nested event delegation
document.querySelector('.parent').addEventListener('click', function(e){
if(e.target.classList.contains('btn-link')){
// Hide checkmark if not active button
checkMarks.forEach(function(check, index){
check.classList.remove('show');
});
// Display checkmark
e.target.closest('div').querySelector('.ck').classList.add('show');
// Display load dynamic button
document.querySelector('.load-content').classList.add('show');
}
});
document.querySelector('.load-content').addEventListener('click', function(){
document.querySelector('.parent').innerHTML = '<div><button class="btn-link btn4">Button 4</button><span class="ck">checked</span></div><div><button class="btn-link btn5">Button 5</button><span class="ck">checked</span></div><div><button class="btn-link btn6">Button 6</button><span class="ck">checked</span></div>'
});
.parent{
padding: 30px;
}
.parent .footer{
margin-top: 30px;
margin-bottom: 30px;
display: flex;
}
.btn-link{
display: block;
width: 100px;
padding: 15px;
margin-bottom: 15px;
}
.load-content{
display: none;
padding: 15px;
}
.load-content.show{
display: block;
}
.ck{
display: none;
}
.ck.show{
display: block;
}
<div class="parent">
<div><button class="btn-link btn1">Button 1</button><span class="ck">checked</span></div>
<div><button class="btn-link btn2">Button 2</button><span class="ck">checked</span></div>
<div><button class="btn-link btn3">Button 3</button><span class="ck">checked</span></div>
</div>
<div class="load"><button class="load-content">Load dynamic content</button></div><div>
jsfiddle
According to MDN: The Element method querySelectorAll() returns a static (not live) NodeList representing a list of elements matching the specified group of selectors.
So I changed that to getElementsByClassName, also the loop process, from forEach to for loop, here you go.
const checkMarks = document.getElementsByClassName('ck'); // Changed that
// Nested event delegation
document.querySelector('.parent').addEventListener('click', function(e){
if(e.target.classList.contains('btn-link')){
// Hide checkmark if not active button
for(let i = 0; i < checkMarks.length ; i++){ // Also that changed, as forEach doesn't work for getElementsByClassName.
checkMarks[i].classList.remove('show');
};
// Display checkmark
e.target.closest('div').querySelector('.ck').classList.add('show');
// Display load dynamic button
document.querySelector('.load-content').classList.add('show');
}
});
document.querySelector('.load-content').addEventListener('click', function(){
document.querySelector('.parent').innerHTML = '<div><button class="btn-link btn4">Button 4</button><span class="ck">checked</span></div><div><button class="btn-link btn5">Button 5</button><span class="ck">checked</span></div><div><button class="btn-link btn6">Button 6</button><span class="ck">checked</span></div>'
});
.parent{
padding: 30px;
}
.parent .footer{
margin-top: 30px;
margin-bottom: 30px;
display: flex;
}
.btn-link{
display: block;
width: 100px;
padding: 15px;
margin-bottom: 15px;
}
.load-content{
display: none;
padding: 15px;
}
.load-content.show{
display: block;
}
.ck{
display: none;
}
.ck.show{
display: block;
}
<div class="parent">
<div><button class="btn-link btn1">Button 1</button><span class="ck">checked</span></div>
<div><button class="btn-link btn2">Button 2</button><span class="ck">checked</span></div>
<div><button class="btn-link btn3">Button 3</button><span class="ck">checked</span></div>
</div>
<div class="load"><button class="load-content">Load dynamic content</button></div><div>
The click handler on .load-content removes/replaces nodes present in your code and create new nodes. But these new nodes do not get picked up because this code:
const checkMarks = document.querySelectorAll('.ck');
Runs only one time and picks the checkmarks present at the page load. Try moving this expression inside .parent click handler to see if that helps.
Ideally you should be removing the click handler on .parent inside your .load-content click handler. Those handlers are still there and this presents memory leaks. You should be able to fix this by converting your anonymous click handler of .parent into a named function. Then you can simply use removeEventListener and addEventListener to remove and re-add the handlers.

What is an HTML/JavaScript equivalent of this user sortable list that works on mobile?

In my Java desktop application I have a list of items, and the user can change their order by selecting items and using the Up/Down buttons to change their position in the list.
What would be an equivalent way to do this in HTML? A <select> would not work because on mobile devices, selects only ever show one row regardless of what the size is set to.
Using JavaScript, you can quite easily implement such a control yourself. Here is a very quick and dirty example that works in modern browsers (most notably, not in Internet Explorer). Since mobile browsers do fire click events when handlers are attached directly to elements, this also works on mobile/touch devices. Of course, you may want to implement a slightly different layout for mobile devices, to make it easier to click elements. It may also be more intuitive to implement drag-and-dropping of elements to sort them on touch devices.
To make this work in a regular old form, a hidden input can be added and updated as well. To this end, we can add data attributes on the options, since they can be read easily with JavaScript. Another option would be to intercept the submit event of the form and doing the submit manually via AJAX, loading the event order then. The below demo implements the first option: populating a hidden input.
function orderInput(list, upButton, downButton, input) {
updateInput(list, input);
// enable selection of list elements
for (let li of list.querySelectorAll('li')) {
li.addEventListener('click', () => {
for (let sibling of li.parentNode.children) {
if (!sibling.isSameNode(li)) {
sibling.classList.remove('selected');
}
}
li.classList.toggle('selected');
});
}
// enable moving an element up
upButton.addEventListener('click', () => {
var li = list.querySelector('li.selected');
if (li.previousElementSibling !== null) {
li.parentNode.insertBefore(li, li.previousElementSibling);
updateInput(list, input);
}
});
// enable moving an element down
downButton.addEventListener('click', () => {
var li = list.querySelector('li.selected');
if (li.nextElementSibling !== null) {
li.parentNode.insertBefore(li, li.nextElementSibling.nextElementSibling);
updateInput(list, input);
}
});
}
function updateInput(list, input) {
var values = [];
for (let li of list.querySelectorAll('li')) {
values.push(li.dataset.value);
}
input.value = values.join(';');
}
// instantiate on our fruits
orderInput(
document.querySelector('ul'),
document.getElementById('up'),
document.getElementById('down'),
document.querySelector('input[name="fruits"]')
);
ul {
padding-left: 0;
list-style-type: none;
border: 1px solid darkgray;
}
li {
padding: 2px 4px;
}
li:nth-child(even) {
background-color: lightgray;
}
li.selected {
background-color: blue;
color: white;
}
<p>Click an item in the list to select it, change its place in the list using the up and down buttons.</p>
<button id="up">Up</button> - <button id="down">Down</button>
<ul>
<li data-value="red-apple">Apple</li>
<li data-value="yellow-banana">Banana</li>
<li data-value="cherries">Cherry</li>
<li data-value="d-fruit">Dragon Fruit</li>
<li data-value="old-berry">Elderberry</li>
<li data-value="not-a-figure">Fig</li>
<li data-value="blue-grape">Grape</li>
</ul>
<input type="hidden" name="fruits" />
For the requirements that you want, try this..
In the function selected() you can write code as to what shpuld happen if an element is selected.
function selected(str){
console.log(str+" is selected.");
}
ul {
position: absolute;
top: 0;
width: 200px;
margin: 0;
padding: 0;
list-style: none;
}
ul a:hover {
color: red;
background: none;
}
li a {
display: block;
text-decoration: none;
}
li a:hover {
text-decoration: none;
color: red;
background: rgba(255, 255, 255, 0.2);
}
li a:focus {
text-decoration: none;
}
<ul style="height:4em; overflow-y:auto;">
<li><a onclick="selected('1')">One</a></li>
<li><a onclick="selected('2')">Two</a></li>
<li><a onclick="selected('3')">Three</a></li>
<li><a onclick="selected('4')">Four</a></li>
<li><a onclick="selected('5')">Five</a></li>
</ul>
I recommend the use of JQuery plugin or small Javascript library like Sortable, that supports change the order using drag and even touch in portable devices.
Example: http://rubaxa.github.io/Sortable/

Font weight when active - CSS

Currently I am trying to make the selected list item STAY bold after selected and go back to normal after a different item is selected. basically I want a "currently selected tab". is there a way I can do this with css or do I need Javascript? Here is my code ->
CSS: (using SASS)
.setup-nav {
float: left;
position: relative;
width: 20%;
padding-right: 20px;
box-sizing: border-box;
font-size: 18px;
color: $base-link-color;
text-align: left;
li {
border-top: none;
border-bottom: none;
&.active {
font-weight: bold;
}
}
HTML:
<ul class="setup-nav">
<li>HTTP</li>
<li>Email</li>
<li>Ruby</li>
<li>Python</li>
</ul>
Hopefully there is an easy way to do this.
Using jQuery:
$(".setup-nav").on( "click", "li", function(){ // attach to Click event
$(".setup-nav li.active").removeClass("active"); // reset all <li> to no active class
$(this).addClass("active"); // add active class to this <li> only
});
http://api.jquery.com/on/ reference for use of .on()
http://api.jquery.com/addClass/ reference for use of .addClass()
Someone may come along with pure JavaScript answer (no jQuery library use) if you prefer that approach.

Blog / Message - How do I fix that empty message divs will piling on / next to each other and make the close button work?

So I'm making a sort of blog posting system or TODO list, however you want to call it.
I want that the following can happen / is possible:
[Working] The user types something in the textarea
[Working] The user clicks on the button.
[Working] A new div will be created with the text of the textarea.
[Working] The textarea will be empty.
[Not Working] The user has got the choice to delete the post by clicking the 'X' on the right side of each '.post' div.
BUT: If I click on the button when there's nothing in the textarea, there appears an empty div, with only an 'X' close button, no background color either. They appear on the same line as the previous message, so you can get a lot of 'X's next to each other.
AND: Clicking the 'X' close button doesn't do anything. No errors in Firefox console.
If it's not clear enough, run this JSFiddle, click the button and I think you'll understand what I mean:
JSFiddle
HTML:
<!DOCTYPE html>
<html>
<head>
<link href="main.css" rel="stylesheet">
<script src="jquery.min.js"></script>
<meta charset="UTF-8">
</head>
<body>
<div id="blog">
<h1>Blog post application</h1>
<div id="post-system">
<textarea id="poster" rows="5" cols="50" placeholder="Update status."></textarea>
<div id="button">Post</div>
<div id="posts">
</div>
</div>
</div>
</body>
</html>
jQuery Script:
<script>
$(document).ready(function () {
$('#button').click(function () {
var text = $('#poster').val();
$('#posts').prepend("<div class='post'>" + text + "<span class='close-post'>×</span></div>");
$('#poster').val('');
});
$('.close-post').click(function () {
('.close-post').parent().hide();
});
});
</script>
CSS:
body {
margin: 0;
padding: 0;
font-family: sans-serif;
}
#blog {
background-color: blue;
margin: 50px;
padding: 50px;
text-align: center;
border-radius: 10px;
color: white;
display: block;
}
#poster {
color: default;
resize: none;
border: 1px solid black;
text-decoration: blink;
font-size: 20px;
display: inline-block;
position: relative;
border-radius: 5px;
border: 2px solid black;
padding-left: 10px;
padding-top: 5px;
}
#button {
background-color: #00FFFF;
color: white;
border: 2px solid white;
border-radius: 5px;
cursor: pointer;
width: 50px;
float: left;
}
.post {
background-color: white;
color: blue;
margin-top: 20px;
width: auto;
display: block;
}
.close-post {
margin-right: 10px;
float: right;
color: red;
cursor: pointer;
}
You appear to have two issues:
1) You don't want a post to be created if the textarea is empty
Simple fix . . . check to see if it is empty, before calling the logic to add the new post (and use jQuery's $.trim() to account for only blank spaces):
$('#button').click(function() {
var text = $.trim($('#poster').val());
if (text !== "") {
$('#posts').prepend("<div class='post'>" + text + "<span class='close-post'>×</span></div>");
$('#poster').val('');
}
});
2) The 'X' buttons are not closing the posts
This also should be a pretty easy fix . . . the reason that they are not working is because the 'X' buttons don't exist when the page is loaded so $('.close-post').click(function() { is not binding to them on page load. You will need to delegate that event binding, so that it will apply to the 'X' buttons that are dynamically added after the page is loaded.
Now, not knowing what version of jQuery that you are using (I can't access jsFiddle from work), I'll point you to the right place to figure out the correct way to do it: https://api.jquery.com/on/
If it is jQuery 1.7 or higher, you would do it like this:
$("#posts").on("click", ".close-post", function() {
$(this).parent().hide();
});
If your version is earlier than that, then investigate the jQuery .delegate() and .live() methods to determine which is the right one to use for your code..
Try this:
$(document).ready(function() {
$('#button').click(function(event) {
event.preventDefault();
var text= $('#poster').val();
if (text === '') {
alert('Nothing to post!');
return;
}
$('#posts').prepend("<div class='post'>" + text + "<span class='close-post'>×</span></div>");
$('#poster').val('');
});
$('#posts').on('click', '.close-post', function() {
$(this).closest('.post').fadeOut();
});
});
JSFiddle
The way you are doing this, the user will only ever see what they are posting - if you're trying for a chat type where users talk to each other then you will need to store what is being typed on the server side and refresh the screen using something like ajax
but in response to your question, you need to bind the close click like this:
$( "#posts" ).on( "click", ".close-post", function() {
$(this).parent().hide(); // $(this) is the clicked icon, the way you did it above wouldn't as if it had the dollar, it would close all .close-post parents
});
See the part about delegated events: http://api.jquery.com/on/

jQuery hide() and show() not working as expected

I am trying to display a hidden div (with a class .details) whenever a mouse hover at an element .tags in a page. This is working but not as expected.
I want the mouse should be able to enter the displayed .details and should even be able to click on its contents as we have here in StackOverflow tags
but the moment the mouse leaves .tags everything disappears. How can I delay the appearance of .details and have it allow mouse to select
its content whenever a mouse hovers over .tags?
HTML code:
<div class = 'tags'>
<div class='details'>
<a href='a.html'> jQuery </a>
<a href='b.html'> PHP </a>
<a href='c.html'> MySQL </a>
<a href='d.html'> Ruby on Rails </a>
</div>
</div>
CSS code:
.details {
background-color: rgb(235,243,243);
border-radius: 7px;
padding: 3px;
width: 240px;
float: left;
position: relative;
margin-top: 5px;
font-weight: 400;
}
jQuery code:
$(document).ready(function(){
$('.details').hide();
$(document).on('mouseover', ".tags", function () {
var $this = $(this);
$this.find('.details').slideDown(100);
});
$(document).on('mouseleave', ".tags", function () {
var $this = $(this);
$this.find('.details').hide();
});
});
Thank you in advance.
Create jsfiddle for answer. The problem is in .parents('.tags'), because $this is already tabs element. And $this.parents('.tags') returns empty jQuery object.
Add this style to your page and see if this helps
.tags {
height: 100px;
width: 200px;
}
the reason is because the .tags div is so small. Even though the .details div populates the .tags div it doesn't force the size of the element to increase.
which is why it is so hard for you to navigate across the .details div
You are hiding the tag that you want to control the mouse over event for.
Also, look at this way of setting up the selector, it makes it far more readable:
$(".tags").on('mouseover', function () {
alert("hi");
});
http://jsfiddle.net/tVEkF/

Categories

Resources