jQuery + Appending a clone multiple times, at different times? - javascript

I have this form which has a section where a user can specify an indefinite number of value pairs, specifically, a language and a level of proficiency.
I have the DOM structured like this:
<ul id="language-list">
<li class="user-language-item">
<select name="language[]" class="user-language-select">...</select>
Level: <select name="proficiency[]">...</select>
<input type="button" value="Remove" class="remove-language" />
</li>
<li class="user-language-item">
<select name="language[]" class="user-language-select">...</select>
Level: <select name="proficiency[]">...</select>
<input type="button" value="Remove" class="remove-language" />
</li>
<li class="user-language-item">
<select name="language[]" class="user-language-select">...</select>
Level: <select name="proficiency[]">...</select>
<input type="button" value="Remove" class="remove-language" />
</li>
</ul>
<input type="button" value="Add another language..." id="add-a-language" />
If the user clicks on the Add another language... button, a new list item containing the same form elements should be inserted to the list.
And here is my code:
$(function(){
//Save a clone of one list item during initialization.
var liItem = $('.user-language-item').last().clone();
$('#add-a-language').click(function(){
//Append the clone to the list item. But this only works once!
$('#language-list').append(liItem);
});
//Note that there might be an instance where there are no list items present, which is why I opted to clone the a list item during initialization.
$('.remove-language').live('click', function(){
$(this).parents('li.user-language-item').fadeOut(500, function(){
$(this).remove();
});
});
});
But the clone seems to be only usable once. Upon clicking the Add a language... button the second time, no list item is appended. (Interestingly, when I output the variable on the console, it still contains the clone!)
One way around this would be saving the HTML mark-up as a string, but I am avoiding this approach as the elements are dynamically loaded via PHP, and I'd rather just change one part of my code whenever I need to modify the mark-up.
How can I possibly make this work?

You will have to clone it every time when you want to add
$('#add-a-language').click(function(){
var liItem = $('.user-language-item').last().clone();
//Append the clone to the list item. But this only works once!
$('#language-list').append(liItem);
});
Demo
As per comment:
Keep one li like this:
<li class="user-language-item hidden" id="placeHolderLi">
<select name="language[]" class="user-language-select">...</select>
Level: <select name="proficiency[]">...</select>
<input type="button" value="Remove" class="remove-language" />
</li>
Where .hidden just marks it to display:none;
.hidden{
display:none;
}
Then while cloning you always clone this li and make it visible so that even if user has deleted all the li, new elements can still be added.
$('#add-a-language').click(function(){
var liItem = $('.user-language-item:first').clone(true).show();
//Append the clone to the list item. But this only works once!
$('#language-list').append(liItem);
});
Demo

Related

List item can't be added using javascript

I checked all the code and there is nothing wrong with it. When I check the "vegetables" radio button, write something in the text field and hit add, it doesn't add the element.
function add() {
var item;
item = document.getElementById("item").value;
if (document.getElementById("1").checked) {
var li = document.createElement(li);
text = document.createTextNode(item);
li.appendChild(text);
document.getElementById("1v").appendChild(li);
}
}
<section>
<h1>Welcome to your shopping List!</h1>
<h2>Type in the item, choose its type, and hit add !!</h2>
<div id="blue">
<form action="">
<label for="task">I need :</label><br>
<input type="text" placeholder="Example : Apples" id="item" name="item"><br>
<br>
<label for="type">which is :</label><br>
<div class="">
<input type="radio" id="1" name="type" value="Vegetables">
<label for="">Vegetables</label><br>
</div>
<br>
<button id="add" onclick="add()">Add !</button>
</section>
</form>
</div>
<br>
<footer id="white">
<div>
<table border="bold">
<th>Vegetables</th>
<tbody>
<tr>
<td>
<ol id="1v"></ol>
</td>
</tr>
</tbody>
</table>
</div>
</footer>
You have several problems:
You are using a form, so when you click the button, the form
submits and you leave the page. In your case, you aren't really going to submit data anywhere, so you really don't even need the form element or to specify name attributes for your fields.
You don't have li in quotes, so you aren't actually creating an
<li> element. Without quotes, the system thinks li is a variable, but since you don't have a variable called that, a new Global variable is created with a value of undefined, so your code executes as: document.createElement("undefined"), which does actually create: <undefined></undefined>, but since that isn't an actual HTML element, nothing is rendered for it, except the text you placed inside it (but no numbering):
var li;
let el = document.createElement(li);
console.log(el);
You are using label incorrectly. A <label> element correlates to a form-field element (i.e. input, select, textarea) as a way to have a "caption" for that element. To use it, you should set its for attribute to the id of the form-field its associated with or you can not use for and just nest the form-field its associated with inside of the label. label is not just for text you want to display.
Your HTML is not nested properly and you are missing elements.
Tables should really not be used for anything but displaying tabular
data. They should not be used for layout. Since you are creating new
ordered list items for each item added, you should not use a table.
But, even when you do, you can't just have th. th must be inside
of tr, which would then be inside of thead.
A footer element is meant to provide "the fine print" content at the end of a section. Producing your list isn't that kind of content and shouldn't be in a footer.
Here's all of that put toghether:
// Get your DOM references just once
const item = document.getElementById("item");
const list = document.getElementById("1v");
const veg = document.getElementById("type");
// Don't use inline HTML event attributes to set up events.
// Do your event binding in JavaScript, not HTML.
document.getElementById("add").addEventListener("click", add);
function add() {
if (veg.checked) {
var li = document.createElement("li"); // <-- Need quotes around the element name
li.textContent = item.value;
list.appendChild(li);
}
}
table,th,td { border:1px solid black; }
<section>
<h1>Welcome to your shopping List!</h1>
<h2>Type in the item, choose its type, and hit add !!</h2>
<div id="blue">
I need : <input type="text" placeholder="Example : Apples" id="item"><br>
<br>
which is : <input type="checkbox" id="type" value="Vegetables"> Vegetables
<div><button id="add">Add !</button></div>
</div>
</section>
<br>
<footer id="white">
<div>
Vegetables
<ol id="1v"></ol>
</div>
</footer>
2 quick fixes to your code (personally, I would rewrite the whole thing):
add type="button" to the button. It will prevent the button from defaulting to a submit.
Syntax error in var li = document.createElement(li);. the li should be in quotes:
var li = document.createElement('li');

How to use checked input values used for math as part of cloned div elements

I have written a script that clones a certain div as required by the user. Within the div there are three checkbox input options and each option as a numeric value. I want the script to allow the user to select a checkbox and then the value will be reflected in another input space and each value that are added will be separated by a comma.
The tricky part is that it should be done for each clone, and that each checkbox has the same class name to which the script should be written. I realize that using unique id's would be better, but I would like it that a for loop could do it for any number of checkboxes under the specific class.
Here is the html script:
<style>
.hidden {
display: none;
}
</style>
<body>
<h2>Test</h2>
<button id="add">Add</button>
<div class="test hidden">
<div class="user_input1">
<label>Input1</label>
<input class="input1" type="text" required>
<label>Input2</label>
<input type="text" name="value2" required>
<div class="user_input2">
<table>
<thead>
<tr>
<th>Pick Option</th>
</tr>
</thead>
<tbody>
<tr id="append">
<td><input class="test" type="checkbox" name="test" value="1">Test1</td>
<td><input class="test" type="checkbox" name="test" value="2">Test2</td>
<td><input class="test" type="checkbox" name="test" value="3">Test3</td>
</tr>
</tbody>
</table>
<input type="text" id="insert" name="check">
<button class="hidden" id="testbtn">Calc</button>
</div>
</div>
</div>
<form action="server/server.php" method="POST">
<div class="paste">
</div>
<button type="submit" name="insert_res">Submit</button>
</form>
</body>
And my attempt for the jQuery:
$(document).ready(function() {
var variable = 0
$("#add").click(function() {
var element = $(".test.hidden").clone(true);
element.removeClass("hidden").appendTo(".paste:last");
});
});
$(document).ready(function(event) {
$(".test").keyup(function(){
if ($(".test").is(":checked")) {
var test = $(".test").val();
};
$("#insert").val(test);
});
$("#testbtn").click(function() {
$(".test").keyup();
});
});
I think a for loop should be used for each checkbox element and this to specify each individual clone, but I have no idea where or how to do this. Please help!
I am assuming you already know how to get a reference to the dom element you need in order to append, as well as how to create elements and append them.
You are right in that you can loop over your dataset and produce dom elements with unique id's so you can later refer to them when transferring new values into your input.
...forEach((obj, index) => {
(produce tr dom element here)
(produce three inputs, give all unique-identifier)
oneOfThreeInputs.setAttribute('unique-identifier', index); // can set to whatever you want, really
(proceed to creating your inputs and appending them to the tr dom element)
targetInputDomElementChild.setAttribute('id', `unique-input-${index}`); // same here, doesn't have to be class
});
Observe that I am using template strings to concat the index number value to the rest of the strings. From then on, you can either reference the index to refer to the correct input or tr using jquery in your keyUp event handler:
function keyUpEventHandler($event) {
const index = $(this).attr('unique-identifier');
const targetInput = $(`#unique-input-${index}`)
// do stuff with targetInput
}
I have created a fiddle to show you the route you can take using the above information:
http://jsfiddle.net/zApv4/48/
Notice that when you click an checkbox, in the console you will see the variable number that designates that set of checkboxes. You can use that specific number to get the input you need to add to and concat the values.
Of course, you still need to validate whether it is being checked or unchecked to you can remove from the input.

jquery mobile listview count items after filter

I need some help. I am trying to display the number of visible listview items after filter has been applied, either by a button click or by applying a text filter. The button clicks simply put text into the text filter and so it's all the same trigger point hopefully.
Html is pretty much like this...
<div>
<form class="ui-filterable">
<input id="myFilter" data-type="search" placeholder="Text Filter...">
</form>
<div data-role="fieldcontain">
<input type="button" data-inline="true" value="More Info Provided" id="more-info-provided-filter" />
<input type="button" data-inline="true" value="In Progress" id="in-progress-filter" />
</div>
<p id="listInfo"></p>
<ol data-role="listview" id="myList" data-filter="true" data-input="#myFilter"></ol>
</div>
And my javascript for each button click is just like this...
$(document).on('click', '#in-progress-filter', function(){
$('input[data-type="search"]').val('in progress');
$('input[data-type="search"]').trigger("keyup");
var volListItemsDisplayed;
volListItemsDisplayed = $("#myList li:visible").length;
document.getElementById("listInfo").innerHTML = "Number of items (filter on): " + volListItemsDisplayed;
});
The javascript fires before the filter is applied. Is there a way that I can attach my function to the filter, like an onchange type of event? You can assume the listview is populated with records containing either of the text strings being applied by the buttons.
Thanks
With JQM you can do it following way:
$(document).on("pagecreate", "#search-page-id", function(e) {
$("#my-list" ).on("filterablefilter", function(e, data) {
var result = $(this).children("li").not(".ui-screen-hidden").length;
console.log("FILTERED ITEMS: ",result);
});
});
If inside your listview you have also list dividers, you may narrow down the filter by excluding them:
var result = $(this).children("li").not("[data-role=list-divider]").not(".ui-screen-hidden").length;

JQuery Multiple Hidden Values

I have the following HTML;
<ul class="list-group">
<li class="list-group-item">
www.andrewspiers.co.uk
<input type="hidden" name="url" value="www.andrewspiers.co.uk">
</li>
<li class="list-group-item">
wikipedia.org
<input type="hidden" name="url" value="wikipedia.org">
</li>
</ul>
The plan is to add a button to each row and then get the value from the hidden field when the button is clicked.
I have this;
alert( $('input[name="url"]').val() );
But that returns the value of the first row no matter which button is clicked.
This should work:
$('button').click(function(){
console.log($(this).closest('li').find('input[name="url"]').val())
})
You need to use $(this) within the click function to refer to that specific element and then traverse the DOM accordingly (multiple ways to skin a cat on this one) to get the hidden input.
jsFiddle example
Assuming your buttons are placed inside the same li, you need a more specific selector:
alert( $(this).siblings('input[name="url"]').val() );
You can add buttons like this :
$('.list-group-item').each(function(){
$(this).append('<input class="clicked" type="button" value="Click me"/>')
})
YOu can then get value of each hidden according to the button like this:
$('.clicked').on('click',function(){
var hiddenval =$(this).parent().find('input[name="url"]').val();
console.log(hiddenval);
})

jQuery product filter using checkboxes and :contains() show/hide

I'm attempting to put together a simple jQuery product filter for a store that lists all products of a category on one page. This can't be achieved through AJAX because of the way the store is set up.
Simply put, all products of the category are on one page. They have varying brands product names and team names. The markup looks something like this (the form at the end is how I'm planning on doing the filter).
<div id="CategoryContent">
<ul>
<li class="product">Brand1 PRODUCT1 TeamA</li>
<li class="product">Brand1 PRODUCT2 TeamB</li>
<li class="product">Brand2 PRODUCT3 TeamB</li>
<li class="product">Brand2 PRODUCT4 TeamC</li>
<li class="product">Brand3 PRODUCT5 TeamA</li>
<li class="product">Brand3 PRODUCT6 TeamD</li>
<li class="product">Brand4 PRODUCT7 TeamD</li>
<li class="product">Brand1 PRODUCT8 TeamA</li>
<li class="product">Brand1 PRODUCT9 TeamA</li>
<li class="product">Brand1 PRODUCT10 TeamB</li>
<li class="product">Brand4 PRODUCT11 TeamD</li>
<li class="product">Brand2 PRODUCT12 TeamA</li>
</ul>
<div style="clear:both;"></div>
<div class="filter">
<form id= "brandfilter" action="">
<h2>Brands:</h2>
<input type="checkbox" name="brand" value="Brand1"/>Brand1 </br>
<input type="checkbox" name="brand" value="Brand2"/>Brand2 </br>
<input type="checkbox" name="brand" value="Brand3"/>Brand3 </br>
<input type="checkbox" name="brand" value="Brand1"/>Brand4 </br>
</form>
<form id="teamfilter" action="">
<input type="checkbox" name="team" value="TeamA"/>TeamA </br>
<input type="checkbox" name="team" value="TeamB"/>TeamB </br>
<input type="checkbox" name="team" value="TeamC"/>TeamC </br>
<input type="checkbox" name="team" value="TeamD"/>TeamD </br>
</form>
I have found this filter works as I want. In console replacing hide with show and Brand1 with Brand2, TeamA, etc works just fine.
$("#CategoryContent li").not(
$("#CategoryContent li:contains(Brand1)")
).hide();
The next step is getting a double filter which works as well:
$("#CategoryContent li").not(
$("#CategoryContent li:contains(Brand1):contains(TeamA)")
).hide();
My problem with getting this working is two fold. 1 is replacing the Brand1 / Team A with variables (hence the formids).
The second is trying to run the script when a checkbox is clicked. It should work if either one is clicked and if both are clicked (meaning that with the script above it would need to reset by showing all and then hiding).
Currently to initiate it I'm running this script but I'm running into problems so I've gone back to just 1 filter.
$("input:checkbox[name='brand']").click(function() {
var brandfilter = $(this).val();
alert (brandfilter);
$("#CategoryContent li:contains(' + brandfilter + ')").parent().hide();
});
The alert that pops up is what I want (i.e. Brand1) but the hide function afterwards doesn't work and when I alert (brandfilter) again in console again I get [object HTMLFormElement]. So I think the variable isn't storing correctly or something?
Here is the simple working basic script http://jsfiddle.net/7gYJc/
Assuming you want to show items which match any currently ticked box, you can use this:
$('input:checkbox').change(showHideProducts);
function showHideProducts()
{
var checked = $('input:checked');
var products = $('.product');
// If all the boxes are unchecked, show all the products.
if (checked.length == 0)
{
products.show();
}
else
{
products.hide();
checked.each
(
function()
{
$('.product:contains("' + $(this).val() + '")').show();
}
);
}
}​
EDIT: Since you want all the boxes to display when nothing is checked, just add an if (length = 0) check and show everything in there (see above).
I can't tell where the problem is but you can try using filter() instead of the :contains selector. With filter() you can create your own custom filtering rules as it takes a function as parameter.
var myProducts = function (brand, team) {
brand = brand || '';
team = team || '';
return $('.product').filter(function () {
var re = new RegExp(brand + '.+' + team, 'i');
return re.test($(this).text());
});
};
myProducts('Brand1', 'TeamA').hide();
myProducts('Brand2').hide();
Example: http://jsfiddle.net/elclanrs/hYZcW/
I came up with the following approach:
Start with all the checkboxes checked, because you want to show all the products. If the user unticks a checkbox, then it should hide products matching that brand/team.
Here is the code I had:
var $filters = $("input:checkbox[name='brand'],input:checkbox[name=team]").prop('checked', true); // start all checked
var $categoryContent = $('#CategoryContent li'); // cache this selector
$filters.click(function() {
// if any of the checkboxes for brand or team are checked, you want to show LIs containing their value, and you want to hide all the rest.
$categoryContent.hide();
$filters.filter(':checked').each(function(i, el) {
$categoryContent.filter(':contains(' + el.value + ')').show();
});
});​
jsFiddle link.
I had the same kind of problem this week with multiple selects. The selectors worked fine, but hide did not work. I was able to solve it using
.css('visibility', 'hidden'); // instead of .hide() and
.css('visibility', ''); // instead of .show()
I do not understand it, but it worked.

Categories