On my website, I have an unordered list that is originally unpopulated. After the page loads, I use jquery and ajax to call a php page that loads data in from a database. It then returns that data to the ajax call so that it can create a bunch of list items and append them to the unordered list.
Each of these list items can be selected. When they are selected, I append a class to them called "selected-li" which just underlines the text. I also have arrows to navigate between the list items (i.e. up-arrow and down-arrow). I thought that the following would work...
$("#down-arrow").click(function(){
$(".selected-li").next().addClass("selected-li");
});
...and it does, just not as expected. I have to click the down arrow twice in order for the class to be added to the next item. For some reason, it thinks that there is an invisible list item in between each of the ones displayed.
As a fix, I did the following:
$("#down-arrow").click(function(){
$(".selected-li").next().next().addClass("selected-li");
});
It works now, but why does this happen? Why does it think that there is an extra li in there?
HTML code:
<div id="up-down-arrows">
<img id="up-arrow" height="35px" width="35px" src="/../images/circle_arrow_up.png"></img><br><br><br>
<img id="down-arrow" height="35px" width="35px" src="/../images/circle_arrow_down.png"></img>
</div>
<div id="article-list"><ul id="unordered-list"></ul></div>
Javascript code on return from ajax call:
function(data){
for(var i = 0; i < data.length; i++){
title = data[i]["name"];
$("#unordered-list").append('<li title="' + title + '">' + title + '</li><br>');
}
}
It's the <br> tag. The .next() function gets the next sibling element, not the next <li> element. Besides it's not valid html to have a <br> between <li> elements.
Even if you find the reason why you have to call .next() twice, is this really going to do what you want? There could be multiple items with the "selected-li" class, and your code will then add that class to the items that are after each of them. That is, if items 3 & 7 are selected, Items 4 & 8 will also be selected after the down button is clicked. Here's a jsfiddle showing this.
LIVE DEMO
.next() or no .next()... The <ul> tag is not supposed to hold <br> but list tag <li>.
Additionally I would suggest going like:
This will also loop the selected LI element on buttons click.
var $ul = $('#unordered-list');
var liData = ""; // Popoulate string and append only once to UL (see below)
var $li; // Reference to future created LI
var liN; // Will hold the number of LI after creation
var sel = 0; // Selected index
$.ajax({
dataType: "json",
url: 'yourFile.json',
success : function(data){
for(var i = 0; i < data.length; i++){
title = data[i].name;
liData += '<li title="' + title + '">' + title + '</li>'; // <br>? c'mon..
}
$ul.append( liData ); // Append only once (performance matters!)
$li = $('li', $ul); // Find appended LI - put in collection
liN = $li.length; // How many?
$li.eq(sel).addClass('selected-li'); // (add class to first)
}
});
$('#up-arrow, #down-arrow').click(function(){
sel = (this.id.match('down') ? ++sel : --sel) % liN ;
$li.removeClass('selected-li').eq(sel).addClass('selected-li');
});
Related
So I am trying to create new divs of a fruit, but I am added an X to help call a special function that will delete it. My only problems is when I dynamically create my div+span I end up with problems when I try to use the onclick function
Here is my HTML code
<html>
<div id="listContents">
</div>
</html>
Also below is my script code
list = ["apple", "strawberry", "banana"]
for(var i = 0; i < list.length; i++){
$("#listContents").append('<div>' + list[i] + '<span class="picker" id="close" onclick="removeFruit(list[i].toString())"> (X)</span></div>');
}
function removeFruit(fruit){
console.log("Here is the fruit you selected");
}
Obviously I want to be able to delete these when the X button is clicked but right now I am having trouble getting the onclick function to work correctly. Also here is a jsfiddle I quickly made
You are creating an span with id close for each item in your list. Ids should be unique throughout the whole document. Since you are not using the id attribute, I suggest you delete it.
Also, you are appending an onclick event in all your span items. I would use event delegation.
Furthermore, since you are converting the elements in your array into HTML elements, you could use map method to do the transformation and generate all the HTML for your items.
Lastly, you can add a data attribute to account for the item index. You can then read that attribute using jQuery data method.
Having that into account, the code could look like:
var list = ["apple", "strawberry", "banana"];
var $list = $("#listContents");
$list.on('click', '.picker', function(ev) {
var idx = $(ev.target).data('idx');
removeFruit(list[idx]);
});
$list.append(list.map(function(item, idx) {
return '<div>' + item + '<span class="picker" data-idx="' + idx + '"> (X)</span></div>';
}).join(''));
function removeFruit(fruit) {
console.log("Here is the fruit you selected");
}
Similar to what others are saying, id should be reserved for only one-time use.
Also, when you are dynamically creating the items you give each onClick the string "removeFruit(list[i].toString())" so it is not actually looking for a function called removeFruit but instead it has the actual string "removeFruit".
When creating the html you could instead give each item a fruitName attribute equal to the fruit name like so.
list = ["apple", "strawberry", "banana"]
for (var i = 0; i < list.length; i++) {
$("#listContents").append('<div>' + list[i] + '<span class="picker" fruitName='+ list[i] + '>(X)</span></div>');
}
and then we can target these using a click listener rather than an onClick and use what fruitName is equal to by using the $(this).attr('fruitName').
$("#listContents").on('click', '.picker', function(e) {
removeFruit($(this).attr('fruitName'))
})
function removeFruit(fruit) {
console.log('You clicked ' + fruit);
}
$(this) will reffer to whichever span you clicked on and the .attr() will look for the fruitName and find what it is equal to.
you could add the id while you are doing the map.
["apple", "strawberry", "banana"].map(x => {
$("#listContents").append(`<div id="${x}">
${x}
<span
class="picker"
onclick="(() => document.getElementById('${x}').remove())()">
(X)
</span>
</div>`)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div id="listContents">
I have a list in JQuery that's called additionalInfo, which is filled in using this JQuery function:
$('#append').on('click', function () {
//check if the following area is valid before moving on, check the jquery validation library
var text = $('#new-email').val();
var li = '<li>' + text + 'input type="hidden" name="additionalInfo" value="'+text+'"/> </li>';
$('#additional-info-list').append(li);
$('#new-email').val('');
});
The point of the function is not only to store the info in a list that can be used later, but also to render a <li> with the info text in it. Right now I have another button on each <li> that when pressed, makes the li vanish, but I also need to add code to it that completely removes the info text from the additionalInfo list. This is the code I have for that method so far:
$('#removeEmail').on('click', 'li>.remove-btn', function (event){
$(event.currentTarget).closest('li').remove();
});
How can I get the segment of info text out of the li and then remove it from additionalInfo?
You have few problems. First of all when you create the new items, your markup is not correct. You were missing the opening bracket of input tag. Also i changed the code for delete so that it listens for the click event on any item with class remove-btn under the li element. This should delete the item when you click the remove link inside the li.
$(function(){
$('#append').on('click', function () {
var text = $('#new-email').val();
var li = '<li>' + text + '<input type="hidden" name="additionalInfo"
value="'+text+'"/>
<a href="#" class="remove-btn" >remove</a></li>';
$('#additional-info-list').append(li);
$('#new-email').val('');
});
$(document).on('click', 'li>.remove-btn', function (event){
var _this =$(this);
_this.closest('li').remove();
});
});
Here is a working jsfiddle
I am attempting to build a simple narrow by filter using given key word buttons on an otherwise static list of items.
The buttons are in an unordered list and when selected get the class ".selected-tag-button" added to them.
The items are divs with class ".item" and get class ".included-item" when they are active. Inside the div is another UL with list items that contain key words that match the text node on the buttons.
Right now it is working, except, instead of using "buttonName" which contains only the key word for the clicked button, I would like to use "buttonArray" which contains an array of all the selected key words.
I assume I will need some kind of function, but I am not sure where to start. If more than one are selected I want the result to be limited to only items that contain ALL of the selected key words. All of the solutions I have been able to figure out will return the divs that contain ANY of the key words in the array.
$(document).ready(function() {
$("li.tag-button").on("click", function() {
// Toggle button
$(this).toggleClass("selected-tag-button");
// Remove included-item class from all items
$(".item" ).removeClass("included-item");
// Pass the button text into variable
var buttonName = $(this).text().slice(2);
// Create array with button text for all active buttons
var buttonArray = $(".selected-tag-button").map(function() {
return $(this).text().slice(2);
}).get();
console.log(buttonArray);
// Add included-item class to divs containing the button text
$('li:contains("' + buttonName + '")').parents().parents().addClass("included-item");
// If all buttons are inactive, add included-item class to all items
if ($(".selected-tag-button").length == 0 ) {
$(".item" ).addClass("included-item");
}
});
});
Consider this fiddle:
http://jsfiddle.net/6qavvth8/
for(i=0; i<buttonArray.length;i++){
contains += ':contains("' + buttonArray[i] + '")';
}
$('ul' + contains).parents().addClass("included-item");
Loop through your button array to build your jquery selector and keep adding :contains()
Slight modification of #bingo's solution. Works perfectly, thanks.
$(document).ready(function() {
$("li.tag-button").on("click", function() {
// Toggle button
$(this).toggleClass("selected-tag-button");
// Remove included-item class from all items
$(".item" ).removeClass("included-item");
// Create array with button text for all active buttons
var buttonArray = $(".selected-tag-button").map(function() {
return $(this).text().slice(2);
}).get();
// Add included-item class to divs containing the button text
var contains = "";
for(i = 0; i < buttonArray.length; i++){
contains += ':contains("' + buttonArray[i] + '")';
}
$('ul' + contains).parents().addClass("included-item");
// If all buttons are inactive, add included-item class to all items
if ($(".selected-tag-button").length == 0 ) {
$(".item" ).addClass("included-item");
}
});
});
I'm trying to add a second class to a group of buttons using a for loop in jQuery, with the second class set to the number of the current iterator(i). The first class for each button is assigned to "users". I want each button to have a dynamic second class so that when clicked, I can use its second class as an index to access a particular key in an object.
The problem I'm having is that each button has its second class set to 0. "users" is an array with length 4, so the buttons should have as their second class 0,1,2,3 in order.
$(document).ready(function(){
for(var i = 0; i < users.length; i++) {
var $currentUser = $('<button class="users '+ i +'"></button>');
$currentUser.text(users[i]);
$currentUser.appendTo('.userList');
}
$(".users").click(function() {
alert($(".users").attr('class').split());
// Alert returns "users 0" for each button.
});
});
The alert at the bottom is just a placeholder for now to check that the classes are set correctly. Thanks!
change code to below . change $(".users") to $(this) when you click on element.
$(".users").attr('class') always return all button element.
$(".users").click(function() {
alert($(this).attr('class').split());
});
When you use a getter method like .attr('class') on a set of elements, it will return the value of the first element in the set. when you say $('.users') it will return all elements with the class users.
In your case you want to have reference to the clicked button, which is available in the click handler as this so you can
$(document).ready(function() {
var users = ['u1', 'u2', 'u3']
for (var i = 0; i < users.length; i++) {
var $currentUser = $('<button class="users ' + i + '"></button>');
$currentUser.text(users[i]);
$currentUser.appendTo('.userList');
}
$(".users").click(function() {
alert(this.className.split(/\s+/));
alert(this.classList); //for ie 10+
});
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
<div class="userList"></div>
I'm trying to write a JavaScript widget in object-oriented JavaScript, or at least what I understand JS' near-equivalent of classes to be.
All I'm trying to achieve is a menu which changes state. In pseudo;
Upon widget load, populate #nav with a <ul> menu containing year group <li>s.
Upon clicking a year group <li>, empty #nav ul of its <li> and re-populate it with tutor group <li>s in that year, as well as appending a return link.
Upon clicking the return link, empty #nav and re-populate it with the original year group <ul> menu whilst being able to utilise all of the above functionality.
Historically, i.e. to date, I've used "embedded" functions to try and keep everything within the same DOM layer/timing. I don't really understand how any of this works, or if this is an appropriate thing to do, but the code itself seemed to work.
Here's an example of the way I've done it previously:
var GroupsDisplayHTML = '<h2>Tutor Groups</h2>' +
'<ul><li><a class="year" href="javascript:void(0);" title="Year 9" id="9">Year 9</a></li>' +
'<li><a class="year" href="javascript:void(0);" title="Year 10" id="10">Year 10</a></li>' +
'<li><a class="year" href="javascript:void(0);" title="Year 11" id="11">Year 11</a></li>' +
'<li><a class="year" href="javascript:void(0);" title="Year 12" id="12">Year 12</a></li>' +
'<li><a class="year" href="javascript:void(0);" title="Year 13" id="13">Year 13</a></li></ul>';
$("div#groups").html(GroupsDisplayHTML);
$('a.year').click( function() {
var Groups;
var Groups_Sorted = [];
Frog.API.get('groups.getAll',
{
'onSuccess': function (data) { Groups = data; },
'onError': function(err) { alert(err); }
});
for (var i = 0; i < Groups.length; i++) {
var Year = $(this).attr("id");
if (Groups[i].name.indexOf(Year) == 0 && Groups[i].name.indexOf('/Tp') != -1) {
var arrayToPush = { 'id': Groups[i].id, 'name': Groups[i].name };
Groups_Sorted.push(arrayToPush);
}
}
GroupsDisplayHTML = '<h2>Year ' + Year + '</h2><ul>';
for(var i = 0; i < Groups_Sorted.length; i++){
GroupsDisplayHTML += '<li><a class="group" href="javascript:void(0);" title="' + Groups_Sorted[i].id + '" id="' + Groups_Sorted[i].id + '">' +
Groups_Sorted[i].name + ' <span style="font-size:10px;color:#bbb;">(' + Groups_Sorted[i].name + ')</span></a></li>';
}
GroupsDisplayHTML += '<li><a class="return" href="javascript:void(0);"><- Back to Year Groups</a></li></ul>';
$("div#groups").html(GroupsDisplayHTML);
$('a.group').click( function() {
var Group_ID = $(this).attr("id");
AssignPoints.getUsers(Group_ID);
});
$('a.return').click( function() {
AssignPoints.displayGroups(data);
});
});
However, now, I'm wondering if the better way to do it is to use jQuery's on function. Here's the code I'm currently writing (*just to try and achieve the changing-state menu):
var TutorGroupPoints = {
URL: 'http://staff.curriculum.local/frog/rewards.php',
currentUser: UWA.Environment.user.id,
groupsObject: { },
sortedArray: [ ],
navHTML: '<h2>Tutor Groups</h2>' +
'<ul>' +
'<li><a class="year" title="Year 9" id="9">Year 9</a></li>' +
'<li><a class="year" title="Year 10" id="10">Year 10</a></li>' +
'<li><a class="year" title="Year 11" id="11">Year 11</a></li>' +
'<li><a class="year" title="Year 12" id="12">Year 12</a></li>' +
'<li><a class="year" title="Year 13" id="13">Year 13</a></li>' +
'</ul>',
init: function() {
/* caching outer this -> AJAX scope problems */
var that = this;
/* retrieve all of the user groups from Frog VLE and store them in an object-level variable */
Frog.API.get('groups.getAll',
{
'onSuccess': function (data) { that.groupsObject = data; },
'onError': function(err) { alert(err); }
});
/* populate the div#nav with our year group UL */
$('#nav').append(this.navHTML);
/* using "on" because the LIs have been created in the DOM? */
$('#nav ul').on("click", "li a", function(e) {
e.preventDefault();
that.yearClick( $(this).attr("id") );
});
},
yearClick: function(year) {
/* run through groupsObject and pull out any tutor groups found in the year we've clicked on
then put those into our sortedArray */
for (var i = 0; i < this.groupsObject.length; i++) {
if (this.groupsObject[i].name.indexOf(year) == 0 && this.groupsObject[i].name.indexOf('/Tp') != -1) {
/* all we need is name and id */
var arrayToPush = { 'id': this.groupsObject[i].id, 'name': this.groupsObject[i].name };
this.sortedArray.push(arrayToPush);
}
}
/* clear the existing UL from div#nav */
$('#nav ul').empty();
/* populate div#nav's UL with LIs of our tutor groups (label being name, attr="id" being id) and
clickable links for each */
for (var i = 0; i < this.sortedArray.length; i++) {
$('#nav ul').append('<li><a class="tutor" id="' + this.sortedArray[i].id + '">' + this.sortedArray[i].name + '</a></li>');
}
/* add a "return" link to view other years' tutor groups */
$('#nav ul').append('<li><a id="return"><- Return</a></li>');
/* upon clicking the return link, empty #nav ul then re-append our navHTML from earlier */
$('#nav ul').on("click", "a#return", function(e) {
e.preventDefault();
$('#nav ul').empty();
$('#nav').append(this.navHTML);
});
/* upon clicking any of our tutor group LIs, display that link's id so that we can use it in
another function to actually display some content */
$('#nav ul').on("click", "a.tutor", function(e) {
e.preventDefault();
alert( $(this).attr("id") );
});
}
};
widget.onLoad = function(){
/* run our "class" */
TutorGroupPoints.init();
}
And the HTML:
<div id="wrapper">
<div id="nav">
</div>
<div id="content">
</div>
</div>
I've created a jsFiddle here, which is a slight manipulation of the code (i.e. I've removed the inaccessible Frog API calls and replaced them with a fixed object). Hopefully this works adequately.
The problem here is that once I click the return button, nothing happens. In addition, I suspect that fixing the return button would then provide me with some links which, when clicked upon, would do nothing.
Ideally, as per the bottom end of the code, I would like to run another function within my TutorGroupPoints class when the user clicks on a specific tutor group. How will this affect my return button, etc? Will they then stop working because they aren't being run from the "current" function?
More than "just" an answer to my code problems, I'd quite like some direction, i.e. is the recent code I've written better than the older stuff? Should I be using on or should I be using embedded jQuery functions and communicative class functions?
Thanks in advance,
Short answer: http://jsfiddle.net/byzgv/4/
I'm not sure about the OOP aspect, but this seems to be your scenario:
when the page is loaded, you insert a list into a div.
you add some event handlers to this list
you click a list item, your event handlers run, and change the list items.
the "Return" list item "does nothing" when you click it.
In your fiddle, clicking the "Return" list item does do something, it empties this list. This is partly what you asked it to do:
$('#nav').on("click", "ul a#return", function(e) {
e.preventDefault();
$('#nav ul').empty();
$('#nav').append(this.navHTML);
});
So this event handler has fired, and the first two instructions have worked. The call to append doesn't work because you're in a different context - this does not refer to the TutorGroupPoints object. So you can fix this by referring to your object explicitly:
$('#nav').on("click", "ul a#return", function(e) {
e.preventDefault();
$('#nav ul').empty();
$('#nav').append(TutorGroupPoints.navHTML);
});
(by the way, you're appending a whole new list, so you need to do $('#nav').empty(); instead of $('#nav ul').empty();)
Then, as you guessed, clicking the "Return" item gives you a new list which doesn't respond to click events. This is because of how you're calling the on function. The jQuery object you call on on must exist when the event handler is being set up. In your init function, the list does exist when you set up the event handlers, as you're appending it to the div before calling on. But later, you trash this list and replace it.
To get your event handlers working on all lists rather than just the first one, you need to attach your event handlers to an element that exists from the start and doesn't change, i.e. the #nav div. So
$('#nav ul').on("click", "li a", function(e) {
can become
$('#nav').on("click", "ul li a", function(e) {