JS/jQuery events/data lost when altering cloned DOM element - javascript

I am pretty new to JS and jQuery and trying to build a plugin for some form controls to dynamically add and remove elements containing inputs and values which should be stored in a database later on.
I am using a list of elements while the first of those elements serves as a template. In preparation to the cloning, a button triggering on click and containing some data is added to remove newly added or already existing elements. Those elements mainly contain input fields with IDs relating to an index.
This issue happens only when altering the HTML which is copied to create a new element in the DOM.
Here is a fiddle displaying the behavior:
$('ul').sortable({
handle: '.handle',
});
$('<button>remove</button>')
.appendTo($('li'))
.click(function() {
$(this).closest('li').remove();
})
const $template = $('ul>li').first().clone(true);
$('<button>another one</button>')
.appendTo($('#add-btn-holder'))
.click(function() {
$clone = $template.clone(true).html(function(i, html) {
return html.replace(/id-\d-/g, 'id-X-');
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<ul>
<li id="id-0-holder">
<span class="handle"><b>=</b></span>
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>
After adding an new element and altering the html, the event handlers and data will be lost. For simplicity I did not include any data but the click event to remove the new row will be lost.
Commenting out line 17 in the script code (string#replace) leaves all events and data intact.
I can store the data values in a temporary variable before altering the html and re-apply them afterwards. I don't know how to deal with events though and hopefully there is an easier solution without temporary variables.
Unfortunately I didn't find anything useful here because most headers sound promising but are solved with a simple $.clone(true) or $.clone(true, true) (which I am already using).
My real world code is a bit more complex. The remove-button is holding the corresponding to-be-removed item within a data attribute and the query is also build around data, therefore I pretty much have to rely on those values. Unfortunately the classes and IDs may alter why I cannot query with conventional methods.
If this was already asked somewhere else, feel free to guide me there, I wasn't able to find anything useful :(

Don't update the HTML, modify the attributes directly.
When you update the HTML, it's reparsed from scratch and any dynamic modifications to the DOM are lost.
$('ul').sortable({
handle: '.handle',
});
$('<button>remove</button>')
.appendTo($('li'))
.click(function() {
$(this).closest('li').remove();
})
const $template = $('ul>li').first().clone(true);
$('<button>another one</button>')
.appendTo($('#add-btn-holder'))
.click(function() {
$clone = $template.clone(true);
$clone.find("[id^=id-]").attr('id', function(i, id) {
return id.replace(/^id-\d+-/, 'id-X-');
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
<ul>
<li id="id-0-holder">
<span class="handle"><b>=</b></span>
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<span class="handle"><b>=</b></span>
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>

Just .clone() the .last() <li> and increment #id and value by 1. Delegate the click event by using the .on() method. BTW I shortened the #ids to save myself some unneeded typing, feel free to use whatever you prefer.
$('ul').sortable({
handle: '.handle',
});
$('<button>Remove</button>')
.appendTo('li')
.on('click', function() {
if ($('li').length === 1) {
return;
} else {
$(this).closest('li').remove();
}
});
let idx;
$('<button>Add</button>')
.appendTo('.add-box')
.on('click', function() {
const $template = $('ul>li').last();
let prev = Number($template[0].id.split('-').pop());
idx = prev + 1;
let $clone = $template.clone(true, true);
$clone[0].id = `i-${idx}`;
$clone.find('input').each(function(i) {
$(this)[0].id = `i-${idx}-f_${i}`;
$(this).val(`v_${idx}_${i}`);
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<ul>
<li id="i-0">
<label class="handle"><b>=</b></label>
<input id="i-0-f_0" value="v_0_0" />
<input id="i-0-f_1" value="v_0_1" />
</li>
<li id="i-1">
<label class="handle"><b>=</b></label>
<input id="i-1-f_0" value="v_1_0" />
<input id="i-1-f_1" value="v_1_1" />
</li>
<li id="i-2">
<label class="handle"><b>=</b></label>
<input id="i-2-f_0" value="v_2_0" />
<input id="i-2-f_1" value="v_2_1" />
</li>
</ul>
<div class="add-box"></div>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>

So with some help from your answers I came up with this solution to dynamically handle all attributes matching the regex. thanks.
$('<button>remove</button>')
.appendTo($('li'));
$('<button>another one</button>')
.appendTo($('#add-btn-holder'));
// remove-button handler
$('li').on('click', 'button', function() {
$(this).closest('li').remove();
});
const $template = $('li').first().clone(true, true);
// add-button handler
$('#add-btn-holder').on('click', function() {
$clone = $template.clone(true, true);
var re = /id-\d-/,
repl = 'id-X-';
$clone.find('*').each(function() {
var $el = $(this);
$($el.get(0).attributes).each(function() {
$el.prop(this.name, this.value.replace(re, repl));
})
});
$('ul').append($clone);
});
ul {
list-style-type: none;
}
li {
background: #f1f1f1;
margin: .4em;
}
[id^="id-X-"] {
background: #ddd;
}
input {
height: 3em;
}
.handle {
cursor: move;
font-size: 1.2em;
padding: 1em;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.0/jquery.min.js"></script>
<ul>
<li id="id-0-holder">
<input id="id-0-field_0" value="val_0_0" />
<input id="id-0-field_1" value="val_0_1" />
</li>
<li id="id-2-holder">
<input id="id-1-field_0" value="val_1_0" />
<input id="id-1-field_1" value="val_1_1" />
</li>
<li id="id-2-holder">
<input id="id-2-field_0" value="val_2_0" />
<input id="id-2-field_1" value="val_2_1" />
</li>
</ul>
<div id="add-btn-holder">
</div>

Related

Why don't my class styles apply to dynamically created elements? Angularjs

My question is simple; why don't my class styles apply to dynamically created elements?
I am creating a search bar here where I generate an li per matching result, and append it to my ul. When I inspect the page, I see the classes are applied to the li's correctly, but the styles from the class itself aren't present. I hard coded a test li and it had the expected styles. What am I missing here in order to have my styles applied to these dynamically generated elements? Surely I don't have to assign every style for the li's in my typescript? Any explanation would be lovely, thank you all! (:
My HTML:
<div class="section">
<h2>Step 1: Choose an Identity Provider (IDP)</h2>
<div class="search">
<input
class="focusable"
(focusout)="handleFocusOut()"
(input)="debounce(search, 300, $event)"
placeholder="Select Identity Provider"
autocomplete="off"
/>
<i class="icon fas fa-search"></i>
<ul id="search-options">
<li class="focusable testing">IMG Salesforce</li>
</ul>
</div>
<!-- <i class="fa fa-plus"></i>-->
</div>
My scss:
.section {
...
.search {
position: relative;
width: 300px;
.icon {
position: absolute;
right: 5px;
top: 3px;
}
input {
width: 300px;
}
ul {
color: red;
li {
cursor: pointer;
&:hover {
background-color: grey;
color: red;
}
.testing {
cursor: pointer;
&:hover {
background-color: grey;
color: red;
}
}
}
}
}
}
My TS:
let ul = document.getElementById('search-options');
this.displayServices.forEach((service) => {
let li = document.createElement('li');
li.classList.add('focusable', 'testing');
li.addEventListener('focusout', this.handleFocusOut);
const img = document.createElement('img');
img.src = this.getImgUrl(service);
img.width = 20;
img.height = 20;
img.style.margin = '0 10px';
li.innerHTML = `${service.name}`;
li.style.display = 'flex';
li.style.alignItems = 'center';
li.style.border = '.5px solid black';
li.style.padding = '8px 0';
li.prepend(img);
ul.appendChild(li);
});
It's hard to be precise without seeing the whole tamale, but generally you should be getting your data in the .TS file and sending that data directly to the view. Your view should be creating those elements on the fly. Not shown in the answer here is the inline styles you were adding to the image and the LI tag - just do those in CSS.
Something like this:
TS:
this.someService.getData.subscribe(displayServices => {
this.displayServices = displayServices;
})
HTML:
<div class="section">
<h2>Step 1: Choose an Identity Provider (IDP)</h2>
<div class="search">
<input
class="focusable"
(focusout)="handleFocusOut($event)"
(input)="debounce(search, 300, $event)"
placeholder="Select Identity Provider"
autocomplete="off" />
<i class="icon fas fa-search"></i>
<ul id="search-options">
<li *ngFor="service in displayServices"
class="focusable testing"
(focusout)="handleFocusOut($event)">
<img [src]="getImgUrl(service)" />
{{service.name}}</li>
</ul>
</div>
<!-- <i class="fa fa-plus"></i>-->
</div>
the classes are applied to the li's correctly, but the styles from the class itself aren't present
If you mean the focusable and testing classes, I don't see them in your SCSS.

onchange and onreset handlers not updating css properties on reset

I have a form (which I am incidentally generating in PHP from a database) that is using CSS to replace checkboxes. When a checkbox is checked, the containing <li> should have an outline added, and when unchecked, the outline should be removed. Using onchange events works to change these at a click, but the outlines remain when the form is reset. I added onreset events, to try to force the removal, but that doesn't seem to change anything.
I've recreated this behavior in the snippet. (I have not hidden the checkboxes, as the snippet system apparently does not duplicate the normal browser behavior of clicking on the <label> to set or clear the checkbox. [EDIT: This was my mistake; I set the wrong for on the labels, and now that behavior works. The rest stands.])
How do I make this work? I could have a function that manually sets each outline in a reset function, but, as I said, the checkboxes are created from a database, so I'd have to write the PHP to write the js function, which seems like the wrong way to go.
function doCheckboxes(clicked_id) {
if (document.getElementById(clicked_id).checked) {
document.getElementById(clicked_id).parentNode.style.outline = "2px solid black";
} else {
document.getElementById(clicked_id).parentNode.style.outline = "0 none black";
}
}
function clearCheckboxes(clicked_id) {
document.getElementById(clicked_id).parentNode.style.outline = "0 none black";
}
li {
display: inline-block;
margin: 10px;
text-align: center;
font-weight: 600;
}
.imageholder {
display: block;
height: 60px;
width: 60px;
background-repeat: no-repeat;
background-clip: content-box;
background-size: cover;
margin: auto;
}
.has-thing1 .imageholder {
background-image: url(path/to/image.png);
}
.has-thing2 .imageholder {
background-image: url(path/to/image.png);
}
.has-thing3 .imageholder {
background-image: url(path/to/image.png);
}
<form action="." method="get">
<fieldset class="subcategory">
<ul>
<li class="has-x has-thing1">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing1" name="has[]" value="thing1">
<label for="x_thing1">
<div class="imageholder"> </div>
Thing1
</label>
</li>
<li class="has-x has-thing2">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing2" name="has[]" value="thing2">
<label for="x_thing2">
<div class="imageholder"> </div>
Thing2
</label>
</li>
<li class="has-x has-thing3">
<input type="checkbox" onChange="doCheckboxes(this.id)" onReset="clearCheckboxes(this.id)" id="x_thing3" name="has[]" value="thing3">
<label for="x_thing3">
<div class="imageholder"> </div>
Thing3
</label>
</li>
</ul>
</fieldset>
<div>
<button type="submit">Submit</button></div>
<button type="reset">Clear Selection</button>
</form>
Create function clearAllCheckboxes
function clearAllCheckboxes(evt) {
const formEl = evt.closest('form')
const allCheckbox = formEl.querySelectorAll('[type=checkbox]')
allCheckbox.forEach(checkbox => {
checkbox.parentNode.style.outline = "0 none black"
})
}
Add an onClick handler to the button "Clear Selection"
<button type="reset" onClick="clearAllCheckboxes(this)">Clear Selection</button>

How to target a div with a same ID

I currently have some ASP.Net code that builds an output and then displays it through a string literal. The idea is that for every Receipt, there is a 'view more' button which toggles extra information that is display: none; to start with. I tried to use the eq() method to attempt to find which one I wanted to toggle because I am doing that inside the ul. My current code is this:
$("#btn-expand").click(function () {
var ldx = $("#list li .active").index(); // Get the list number so we know what ID to work with
var idx = $("#expand").next().index(); // Get the next ID index point
// Check that it isn't going negative
if (idx == -1 || ldx == -1) {
// If it is, reset ldx
ldx = 0;
idx = 0;
}
$("#expand").eq(ldx).toggle(); // Toggle that one
console.log(ldx);
});
The first button works fine and console.log shows 0 however, all the others do not show anything. A sample of my HTML looks like this:
<ul id="list">
<li class="active">
Something <br />
Something Again <br />
<span id="btn-expand">[View more]</span>
<div id="expand">
Something hidden
</div>
</li>
This <br />
Shows <br />
<span id="btn-expand">[View more]</span>
<div id="expand">
This is hidden until toggled
</div>
<li></li>
</ul>
There is a lot more li elements in the ul but that is how it is structured. I am also using <span class="btn" id="btn-next">Next</span> to loop through each li in the ul so I am really confused why the same method for doing it with the `#expand' won't work.
If anyone could point me in the right direction, I'd be appreciated. Thanks.
$(document).ready(function() {
$("#btn-next").click(function() {
var $list = $("#list li");
var idx = $(".active").removeClass("active").next().index();
if (idx == -1) {
idx = 0;
}
$list.eq(idx).addClass("active");
});
$("#btn-prev").click(function() {
var $list = $("#list li");
var idx = $(".active").removeClass("active").prev().index();
if (idx == -1) {
idx = 0;
}
$list.eq(idx).addClass("active");
});
$("#btn-expand").click(function() {
// Get the list number so we know what ID to work with
var ldx = $("#list li .active").index();
// Get the next ID index point
var idx = $("#expand").next().index();
// Check that it isn't going negative
if (idx == -1 || ldx == -1) {
// If it is, reset ldx
ldx = 0;
idx = 0;
}
// Toggle that one
$("#expand").eq(ldx).toggle();
console.log(ldx);
});
});
#list {
list-style: none;
}
.active {
display: block;
}
#btn-expand {
cursor: pointer;
font-size: 9px;
margin-left: 10px;
}
#expand {
display: none;
}
li {
display: none;
}
.btn {
background: none;
padding: 10px;
border: 1px solid #000;
margin-left: 50px;
cursor: pointer;
}
<script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<ul id="list">
<li class="active">
Something here
<br />Something here again
<span id="btn-expand"> [View More] </span>
<br />
<br />
<span id="expand">
This is hidden, shh..
</span>
</li>
<li>
You can see this
<br />Toggling shouldn't effect me
<span id="btn-expand"> [View More] </span>
<br />
<br />
<span id="expand">
But toggling should effect me!
</span>
</li>
</ul>
<span class="btn" id="btn-prev">Prev</span> - <span class="btn" id="btn-next">Next</span>
id should be unique in same document, replace the duplicate ones by general class, r.g :
<ul id="list">
<li class="active">
Something <br />
Something Again <br />
<span class="btn-expand">[View more]</span>
<div class="expand">
Something hidden
</div>
</li>
This <br />
Shows <br />
<span class="btn-expand">[View more]</span>
<div class="expand">
This is hidden until toggled
</div>
<li>
</li>
</ul>
Then replace id selector # in you script by class selector . :
$(".btn-expand").click(function() {
// Get the list number so we know what ID to work with
var ldx = $("#list li .active").index();
// Get the next ID index point
var idx = $(".expand").next().index();
// Check that it isn't going negative
if (idx == -1 || ldx == -1) {
// If it is, reset ldx
ldx = 0;
idx = 0;
}
// Toggle that one
$(".expand").eq(ldx).toggle();
console.log(ldx);
});
You could use just next() instead of all the code in your event :
$(this).next(".expand").toggle();
//OR
$(this).next().toggle();
Hope this helps.
$(".btn-expand").click(function() {
$(this).next(".expand").toggle();
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<ul id="list">
<li class="active">
Something <br />
Something Again <br />
<span class="btn-expand">[View more]</span>
<div class="expand">
Something hidden
</div>
</li>
<li>
This <br />
Shows <br />
<span class="btn-expand">[View more]</span>
<div class="expand">
This is hidden until toggled
</div>
</li>
</ul>
Change id's to classes and all you need then is:
$(".btn-expand").click(function() {
$(this).next().toggle();
$(this).text(function(_, oldText){
return oldText.indexOf('More') === -1 ? 'View More' :'View Less';
})
});
$(document).ready(function() {
$("#btn-next").click(function() {
var $list = $("#list li");
var idx = $(".active").removeClass("active").next().index();
if (idx == -1) {
idx = 0;
}
$list.eq(idx).addClass("active");
});
$("#btn-prev").click(function() {
var $list = $("#list li");
var idx = $(".active").removeClass("active").prev().index();
if (idx == -1) {
idx = 0;
}
$list.eq(idx).addClass("active");
});
$(".btn-expand").click(function() {
// Toggle
$(this).next().toggle();
});
});
#list {
list-style: none;
}
.active {
display: block;
}
#btn-expand {
cursor: pointer;
font-size: 9px;
margin-left: 10px;
}
li {
display: none;
}
.btn {
background: none;
padding: 10px;
border: 1px solid #000;
margin-left: 50px;
cursor: pointer;
}
.expand-inner {
display:inline-block;
width:100%;
}
<script src="//code.jquery.com/jquery-1.12.0.min.js"></script>
<script src="//code.jquery.com/jquery-migrate-1.2.1.min.js"></script>
<ul id="list">
<li class="active">
Something here
<br />Something here again
<span class="btn-expand"> [View More] </span>
<span style="display:none;">
<div class="expand-inner">This is hidden, shh..</div>
</span>
</li>
<li>
You can see this
<br />Toggling shouldn't effect me
<span class="btn-expand"> [View More] </span>
<span style="display:none;">
<div class="expand-inner">But toggling should effect me!</div>
</span>
</li>
</ul>
<span class="btn" id="btn-prev">Prev</span> - <span class="btn" id="btn-next">Next</span>

change background image of a div when checkbox is checked

I hide the default checkbox and use a div with custom checkbox image instead:
<aui:form>
<c:if
test="<%=company.isAutoLogin() && !PropsValues.SESSION_DISABLED%>">
<div class="rememberImage ftr" id="rememberImg">
<aui:input checked="<%=rememberMe%>" name="rememberMe" id="rememberMe"
type="checkbox" cssClass="remember"/>
</div>
</c:if>
</form>
//omiited
<aui:script use="aui-base">
function changeBG() {
if (this.checked) {
document.getElementById('rememberImg').style.backgroundImage = 'url(../img/chk_none.png)';
} else {
document.getElementById('rememberImg').style.backgroundImage = 'url(../img/chk_check.png)';
}
}
document.getElementById('_58_rememberMe').addEventListener('change', changeBG);
var password = A.one('#<portlet:namespace />password');
if (password) {
password.on(
'keypress',
function(event) {
Liferay.Util.showCapsLock(event, '<portlet:namespace />passwordCapsLockSpan');
}
);
}
</aui:script>
This does not work at all. Any suggestions?? Much appreciated!
UPDATE: add more lines of code that I think maybe have problems
I saw a proper answer already above, but to avoid intruding HTML tags with JS listeners, what considered as not the best practice, I will offer you this solution...
function changeBG() {
if (this.checked) {
document.getElementById('myElement').style.backgroundImage = 'url(.....)';
} else {
document.getElementById('myElement').style.backgroundImage = 'url(.....)';
}
}
document.getElementById('myInput').addEventListener('change', changeBG);
Hope it will help you :)
You can do it like this: Add an onclick attribute to the checkbox that triggers a toggle function. As I cant test it with your code (missing the rest) I can only provide you an enxample where the body background gets changed
<input id="check" type="checkbox" onclick="toggle();"> Click me
<script>
function toggle() {
if( document.getElementById("check").checked ) {
document.getElementsByTagName("body")[0].style.backgroundColor="red";
}
}
</script>
div{
margin: 20px 0;
}
input[type=checkbox] {
display: none
}
input[type=checkbox]+label {
background: url(http://s17.postimg.org/phsoii5vf/check.png) no-repeat;
padding-left: 30px;
padding-bottom: 5px;
padding-top: 2.5px;
height: 18px;
}
input[type=checkbox]:checked+label {
background: url(http://s2.postimg.org/zbjg138np/check_tick.jpg) no-repeat;
}
<div>
<input type="checkbox" id="chk1">
<label for="chk1">Custom Checkbox1</label>
</div>
<div>
<input type="checkbox" id="chk2">
<label for="chk2">Custom Checkbox2</label>
</div>
<div>
<input type="checkbox" id="chk3">
<label for="chk3">Custom Checkbox3</label>
</div>
<div>
<input type="checkbox" id="chk4">
<label for="chk4">Custom Checkbox4</label>
</div>
not required javascript.you can do it from css.
<div>
<input type="checkbox" id="chk">
<label for="chk">Custom Checkbox1</label>
</div>
input[type=checkbox] {
display: none
}
input[type=checkbox]+label {
background: url(../images/check.png) no-repeat;
padding-left: 30px;
padding-bottom: 5px;
padding-top: 2.5px;
height: 18px;
}
input[type=checkbox]:checked+label {
background: url(../images/check_tick.png) no-repeat;
}
you can try this one:
document.getElementById('rememberImage').style.background = 'url(../img/chk_check.png)';
function SetBackground(elm){
if($(elm).is(":checked"))
{
$("#rememberImage").css({"background-color":"gray"});
}
else
{
$("#rememberImage").css({"background-color":"white"});
}
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="rememberImage ftr" id="rememberImage">
<input checked="<%=rememberMe%>" name="rememberMe" id="rememberMe" type="checkbox" onchange="SetBackground(this)"/>
</div>
I don't know how your custom checkbox works but, you can add onchange event and check if checkbox is checked then change the background of div.
Best regards

jQuery Help - minimizing code (hide/show divs) and resetting "form" to default state

Hello guys this is a horrid question to word in one phase so please excuse the above.
I am making a "tag search" the user selects three tags, one at a time from a list of tags have a look at this demo quickly (as it's easier to understand) http://codepen.io/naniio/pen/pJNexm
once all 3 tags are selected the user hits search and it searches my database for the related tagged content.
the problem I have is with the jQuery:
$('.term-1').on('change', function() {
// Gets radio value and adds into tagbox 1
$('#tagbox-1').html(this.checked ? this.value : '');
// Add class to draw users attention to new tag box (2)
$( "#tagbox-2" ).addClass( "highlight" );
// hides tags for tagbox 1
$( ".term-1-tags" ).addClass( "hide-tags" );
// Shows Tags for tagbox 2
$(".term-2-tags").removeClass("state");
});
$('.term-2').on('change', function() {
// gets radio value and adds into tagbox 2
$('#tagbox-2').html(this.checked ? this.value : '');
// Add class to draw users attention to new tag box (3)
$("#tagbox-3" ).addClass( "highlight");
// hides tags for tagbox 2
$(".term-2-tags").addClass( "hide-tags");
// Shows Tags for tagbox 3
$(".term-3-tags").removeClass("state");
});
$('.term-3').on('change', function() {
// gets radio value and adds into tagbox 3
$('#tagbox-3').html(this.checked ? this.value : '');
// hides tags for tagbox 3
$(".term-3-tags").addClass("hide-tags");
});
$('#reset').click(function() {
// reset to default
});
The way i've done it (the hiding and showing divs) seems bloated and I believe there must be a better way? I also have no idea how the reset button is going to work (return the tag search to it's page load state and I do not want to refresh the page to do it)
Any help would be great!
Just one common event is enough in this case.
We can use the index of the Item from the class itself and a short logic to remove the redundancy of code.
note : Here in stackoverflow's fiddle, Reset button won't work as the fiddle is embedded in iframe. But it will work in normal forms.
$('.term-1,.term-2,.term-3').on('change', function() {
var index = $(this).attr('class').split('-')[1];
var nextIndex =parseInt(index)+1;
// Gets radio value and adds into tagbox 1
$('#tagbox-'+index).html(this.checked ? this.value : '');
// hides tags for tagbox 1
$( ".term-"+index+"-tags" ).addClass( "hide-tags" );
if($(".term-"+index)){
// Add class to draw users attention to new tag box (2)
$( "#tagbox-"+nextIndex ).addClass( "highlight" );
// Shows Tags for tagbox 2
$(".term-"+nextIndex+"-tags").removeClass("state");
}
});
$('#reset').click(function() {
$('form')[0].reset();
});
/* Eric Meyer's Reset CSS v2.0 - http://cssreset.com */
html,body,div,span,applet,object,iframe,h1,h2,h3,h4,h5,h6,p,blockquote,pre,a,abbr,acronym,address,big,cite,code,del,dfn,em,img,ins,kbd,q,s,samp,small,strike,strong,sub,sup,tt,var,b,u,i,center,dl,dt,dd,ol,ul,li,fieldset,form,label,legend,table,caption,tbody,tfoot,thead,tr,th,td,article,aside,canvas,details,embed,figure,figcaption,footer,header,hgroup,menu,nav,output,ruby,section,summary,time,mark,audio,video{border:0;font-size:100%;font:inherit;vertical-align:baseline;margin:0;padding:0}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:before,blockquote:after,q:before,q:after{content:none}table{border-collapse:collapse;border-spacing:0}
body {
font-size: 18px;
line-height: 20px;
font-family: 'Open Sans', sans-serif;
}
p {
line-height: 22px;
color: #111111;
}
.container {
width: 500px;
padding: 15px;
margin: 50px auto;
}
.tagbox {
border: 1px solid #F2F2F2;
display: inline-block;
padding: 15px;
cursor: pointer;
margin:15px 15px 15px 0;
-webkit-border-radius: 3px;
-moz-border-radius: 3px;
border-radius: 3px;
}
.select_box {
box-sizing: border-box;
margin: 30px 0;
}
ul.options {
list-style-type: none;
font-size: 0px;
border-style: solid;
border-width: 1px 1px 1px 4px;
margin: 0;
padding: 15px;
}
ul.options li{
display: inline;
}
ul.options li label {
font-size: 18px;
padding: 5px;
background: #FFFFFF;
}
ul.options li label:hover {
/*background: #0000EE;*/
}
label {
cursor: pointer;
}
.noselect {
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.highlight{
display:inline-block;
border:1px solid #F2F2F2;
border-left-style: solid;
border-left-width: 3px;
}
.ta{border-left-color: #E13632;}
.tb{border-left-color: #4CB849;}
.tc{border-left-color: #E89A02;}
.hide-tags, .state{
display:none;
}
button{
margin:15px 0;
border:0;
padding:10px 30px;
border-radius:5px;
font-size:16px;
cursor:pointer;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<form action="">
<span id="tagbox-1" class="tagbox ta noselect highlight">Select 1</span>
<span id="tagbox-2" class="tagbox tb noselect">Select 1a</span>
<span id="tagbox-3" class="tagbox tc noselect">Select 1b</span>
<ul id="" class="options term-1-tags ta">
<li><label for="item-1" class="">tag 1</label></li>
<li><label for="item-2" class="">tag 2</label></li>
<li><label for="item-3" class="">tag 3</label></li>
<li><label for="item-4" class="">tag 4</label></li>
</ul>
<input type="radio" id="item-1" class="term-1" name="item-1" value="tag1" hidden>
<input type="radio" id="item-2" class="term-1" name="item-1" value="tag2" hidden>
<input type="radio" id="item-3" class="term-1" name="item-1" value="tag3" hidden>
<input type="radio" id="item-4" class="term-1" name="item-1" value="tag4" hidden>
<!-- end -->
<ul id="" class="options term-2-tags state tb">
<li><label for="tag1a" class="">tag 1a</label></li>
<li><label for="tag2a" class="">tag 2a</label></li>
<li><label for="tag3a" class="">tag 3a</label></li>
<li><label for="tag4a" class="">tag 4a</label></li>
</ul>
<input type="radio" id="tag1a" class="term-2" name="item-2" value="tag1a" hidden>
<input type="radio" id="tag2a" class="term-2" name="item-2" value="tag2a" hidden>
<input type="radio" id="tag3a" class="term-2" name="item-2" value="tag3a" hidden>
<input type="radio" id="tag4a" class="term-2" name="item-2" value="tag4a" hidden>
<!-- end -->
<ul id="" class="options term-3-tags state tc">
<li><label for="tag1b" class="">tag1b</label></li>
<li><label for="tag2b" class="">tag2b</label></li>
<li><label for="tag3b" class="">tag3b</label></li>
<li><label for="tag4b" class="">tag4b</label></li>
</ul>
<input type="radio" id="tag1b" class="term-3" name="item-3" value="tag1b" hidden>
<input type="radio" id="tag2b" class="term-3" name="item-3" value="tag2b" hidden>
<input type="radio" id="tag3b" class="term-3" name="item-3" value="tag3b" hidden>
<input type="radio" id="tag4b" class="term-3" name="item-3" value="tag4b" hidden>
<!-- end -->
<br>
<button id="search" type="submit">Search</button>
<button id="reset">Reset</button>
</form>
</div>
Using custom data-* attributes, you can reduce your three change event handlers to one change event handler
<input data-current="1" id="item-1" class="term-1" name="item-1" value="tag1>
$('[data-current]').on('change', function() {
var currentId = parseInt($(this).attr('data-current'));
var siblingId = currentId + 1;
// Gets radio value and adds into tagbox 1
$("#tagbox-" + currentId).html(this.checked ? this.value : '');
// hides tags for tagbox 1
$(".term-" + currentId + "-tags").addClass("hide-tags");
if (currentId != 3) {
// Add class to draw users attention to new tag box (2)
$("#tagbox-" + siblingId).addClass("highlight");
// Shows Tags for tagbox 2
$(".term-" + siblingId + "-tags").removeClass("state");
}
});
Regarding from reset, simply do
$('#reset').click(function() {
$('form')[0].reset();
});
http://codepen.io/anon/pen/YXpoEm

Categories