How to separate and position ID variables - javascript

I have an ID named TIMZ I am trying to position each of my digital times in this ID separately but instead they are all conjoined. I partially understand this is due to the style but how I would I correct my code so that I can position each TimZ separately instead of being together as they are showed in the image. Thanks!
Image of TIMZ ID and what does:
HTML CODE FOR ID:
<div id="timZ" class="row" style="width: 90%;">
</div>
<script>
// window.localStorage.removeItem('time');
var s=window.localStorage.getItem('time');
var s=JSON.parse(s);
// console.log(s);
s.forEach(function(time, index) {
$('<div/>', {
"class": 'timZonM col',
'data-timezone': `${time.offset}`,
}).appendTo('#timZ');
CSS Code:
div.timZonM { /* military time */
/* width: 3em; */
/* margin:.3em; */
color: white;
border: 4px solid crimson;
font-size: 110px;
font-family: 'Monserrat';
/* letter-spacing: .05em; */
}
div.timZonM::after {
content: attr(data-seconds);
color:white;
}

Related

replace element, nested elements tag type javascript/jquery

I have a <div> wrapped around a set of nested <div> elements; I'd like to present this as a drop-down menu. The element is part of a plugin I'm using on a WordPress website but this should still be adjustable using custom script.
I would like to replace the encasing div with a <ul>, and turn all of the inner <div>s into <li> elements, so I can show this as a drop-down. Either that or a <select>, with nested <option> tags.
Is there a way I can change the HTML tag type , or replace the div, using pure JavaScript, or jQuery?
From:
<div class="slots">
<div class="availableslot"></div>
<div class="availableslot"></div>
</div>
to:
<ul class="slots">
<li class="availableslot"></li>
<li class="availableslot"></li>
</ul>
or:
<select class="slots">
<option class="availableslot"></option>
<option class="availableslot"></option>
</select>
The isolated code: https://codepen.io/bolti95/pen/rNpMdJx
//div into list
const slots_list = document.createElement("ul");
var slots = document.getElementsByClassName("slots")
console.log(slots)
slots.insertBefore(slots_list, slots.children[0])
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<!DOCTYPE html>
<html>
<head>
<meta charset="<?php bloginfo('charset'); ?>">
<meta name="viewport" content="width=device-width, initial-scale=1">
</head>
<body>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
</body>
</html>
To convert the .availableslot div elements to a ul/li list you can use a combination of wrapAll() and replaceWith(), like this:
$('.availableslot').wrapAll('<ul class="slots" />').replaceWith(function() {
return `<li class="availableslot">${this.innerHTML}</li>`
});
$('.availableslot').wrapAll('<ul class="slots" />').replaceWith(function() {
return `<li class="availableslot">${this.innerHTML}</li>`
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
The same technique can be used to convert them to a select/option form control:
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
$('.availableslot').wrapAll('<select />').replaceWith(function() {
let time = this.querySelector('a').innerText;
return `<option value="${time}">${time}</option>`;
});
.slots {
width: 100% !important;
height: max-content;
border: none !important;
box-shadow: none !important;
margin: auto;
}
.slots div a {
color: #77635A;
background: none !important;
}
.slots div a:hover {
background-color: #F7F7F7 !important;
transition: 0.6s;
}
.availableslot {
width: 120px;
height: 55px;
border-radius: 30px;
margin-bottom: 25px;
margin: 10px;
border: none !important;
border-width: 0px;
text-align: center;
font-size: 20px !important;
background-color: #EFE8DA;
color: #77635A;
}
.availableslot a {
margin: 10px;
}
.availableslot:hover {
outline: 1px solid #5FDEAB;
background-color: #F7F7F7;
transition: 0.6;
}
.availableslot a:hover {
background-color: #F7F7F7;
color: #5FDEAB;
transition: 0.6s;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="slots">
<span>03/22/2022</span>
<br>
<div class="availableslot"><a>11:00</a></div>
<div class="availableslot"><a>12:00</a></div>
<div class="availableslot"><a>13:00</a></div>
<div class="availableslot"><a>14:00</a></div>
<div class="availableslot"><a>15:00</a></div>
</div>
One approach, using plain JavaScript, is below; explanatory comments are in the code:
// creating a named function, using Arrow syntax, that allows the user to pass in an
// Object of updated preferences:
const replaceWith = (opts) => {
let settings = {
// String, sets the element-type to replace the outer wrapper:
outerTo: 'select',
// String, sets the element-type to replace the inner wrapper:
innerTo: 'option',
// String, CSS selector to select the relevant elements:
source: '.slots',
// Boolean, true: replaces the element when its replacement is inserted into the document,
// false: hides the element when its replacement is inserted into the document:
replace: true,
// Boolean, true: preserves the child-nodes of the inner-elements being replaced,
// false: preserves the text-content of the inner-element, but does not
// keep the child-nodes that may have contributed text:
preserveHTMLChildren: false,
};
// while I'm sure there's an easy way to use destructruring to avoid this step, I chose to
// use this approach, wherein we take the keys of the opts Object passed to the function
// or - if no Object is passed - we look at the empty Object-literal (to avoid errors),
// we then iterate over the Array of Object-keys using Array.prototype.forEach(), again
// using Arrow syntax:
Object.keys(opts || {}).forEach(
// the 'key' passes in a reference to the current key of the Array of Object-keys
// over which we're iterating, and in the function we set the Object-key of the
// settings Object to the value of the opts Object key-value:
(key) => settings[key] = opts[key]
);
// to reduce unnecessary typing, we alias the document to the D variable:
let D = document,
// we create an element according to the desired elements:
outer = D.createElement(settings.outerTo),
inner = D.createElement(settings.innerTo),
// we use document.querySelectorAll(), using the supplied, or default,
// CSS selector, to retrieve all matching elements:
sources = [...D.querySelectorAll(settings.source)];
// we use NodeList.prototype.forEach() to iterate over the NodeList of matching
// elements returned to the sources variable:
sources.forEach(
// using Arrow syntax we pass in a reference to the current Node ('source') of
// the NodeList:
(source) => {
// we clone the created-element for the outer-element's replacement:
let outerClone = outer.cloneNode();
// we copy the classList:
outerClone.classList = source.classList;
// we use an Array-literal with the spread syntax to create an Array of
// the child-nodes of the current 'source', and then iterate over that
// Array of Nodes using Array.prototype.forEach():
[...source.children].forEach(
// we pass in a reference to the current child-element of the parent
// 'source', in the variable named 'child':
(child) => {
// we clone the created-element for the inner element:
let innerClone = inner.cloneNode();
// if settings.preserveHTMLChildren is exactly equal to Boolean true:
if (settings.preserveHTMLChildren === true) {
// while there is a firstChild node (whether comment, text, HTMLElement...):
while (child.firstChild) {
// we copy that firstChild Node accross to the innerClone element:
innerClone.append(child.firstChild);
}
} else {
// we update the text-content of the innerClone to be equal to the
// original text-content:
innerClone.textContent = child.textContent;
}
// copying the classList of the existing child to the innerClone:
innerClone.classList = child.classList;
// we append the current innerClone to the outerClone:
outerClone.append(innerClone);
});
// we then access the current 'source' element's parentNode, and
// use ParentNode.insertBefore() to insert the newly-created
// outerClone before the current 'source' element's next-sibling
// (effectively inserting it after the current 'source' element):
source.parentNode.insertBefore(outerClone, source.nextSibling);
// if the user wants to replace the element, so settings.replace is
// exactly-equal to (Boolean) true:
if (settings.replace === true) {
// we remove the current 'source' element, using Node.remove():
source.remove();
} else {
// otherwise we set source.hidden to 'true' to hide the element
// using the 'hidden' HTML attribute (though in the demo I chose
// to modify the opacity to show that the element does, in fact,
// remain in the document); this should probably be modified when
// in use:
source.style.opacity = 0.4;
// source.hidden = true;
}
})
};
// calling the function:
replaceWith({
// using a non-default CSS Selector:
source: '#demo1'
});
// calling the functiona again:
replaceWith({
// modifying the CSS selector again:
source: '#demo2',
// using a <ul> to 'replace' the outer element:
outerTo: 'ul',
// using an <li> to wrap the inner contents:
innerTo: 'li',
// setting Boolean false, to not replace the original
// element(s), so inserting the new element(s) and hiding
// the original(s):
replace: false,
});
replaceWith({
source: '#demo3',
outerTo: 'ul',
innerTo: 'li',
// setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified:
preserveHTMLChildren: true,
});
replaceWith({
source: '#demo4',
// again, setting preserveHTMLChildren to true means the <span> elements
// contained within the inner-<div> elements will be preserved when
// the HTML is modified; but while those <span> elements may be present
// in the DOM (in FF 98/Ubuntu 21.10), they are not styled by the CSS
// (and an <option> element has no valid child-elements), so this isn't
// necessarily going to be respected by the browser:
preserveHTMLChildren: true,
});
*,
::before,
::after {
box-sizing: border-box;
font: normal 400 1rem / 1.5 sans-serif;
list-style-type: none;
margin: 0;
padding: 0;
}
.slots {
border: 1px solid palegreen;
margin: 1em auto;
padding: 0.5em;
width: 20rem;
}
.slots:not([id]) {
border: 1px solid rebeccapurple;
}
.slots span {
color: #f90;
}
<div id="demo1" class="slots">
<div class="availableslot">available 1</div>
<div class="availableslot">available 2</div>
</div>
<div id="demo2" class="slots">
<div class="availableslot">available 3</div>
<div class="availableslot">available 4</div>
</div>
<div id="demo3" class="slots">
<div class="availableslot">available <span>5</span></div>
<div class="availableslot">available <span>6</span></div>
</div>
<div id="demo4" class="slots">
<div class="availableslot">available <span>7</span></div>
<div class="availableslot">available <span>8</span></div>
</div>
JS Fiddle demo.
I have to admit that I'm curious as to why the <select> elements aren't being positioned correctly (in terms of margin-inline: auto), but that's a problem for another time.
If there are any questions then please don't hesitate to leave a comment below.
References:
Array.prototype.forEach().
Array literals [ /*...*/ ].
Arrow functions.
document.createDocumentFragment().
document.createElement().
document.querySelectorAll().
Element.children().
Element.classList.
Element.remove().
Node.cloneNode().
Node.firstChild.
Node.insertBefore().
Node.parentNode.
NodeList.prototype.forEach().
Object.keys().
Spread syntax ....
while (...) {...} statement.

CSS style shows after refresh and then goes invisible with Knockout

So I have a new notification style ring and green circle with unread notifications in it this circle only is visible when you have new notifications.
when page is refreshed even if you dont have a notification the circle is visible for a second and then goes invisible
If there is a new notification still when refreshed circle shows up empty or with zero and then goes invisible and then with correct number
HTML:
<div class="bell">
<div class="unseen-notification-show" data-bind="visible: UnSeenMessagesCount() > 0, text: UnSeenMessagesCount()" style="display:none"></div>
</div>
CSS:
.unseen-notification-show {
content: '';
display: block !important;
position: absolute;
top: -10px;
right: -8px;
width: 17px;
height: 17px;
border: 1px solid #ffffff;
background-color: #8cdb16;
border-radius: 8px;
cursor: pointer;
z-index: 3;
color: white;
text-align: center;
line-height: 15px;
font-size: 11px;
font-family: 'Times New Roman', Times, serif;
}
self.searchModel = new AuthorizedSearchViewModel();
self.Header = ko.observable(new HeaderModel());
self.UnSeenMessagesCount = ko.observable(0);
self.Messages = ko.observableArray();
self.CanShowRemindProfile = ko.observable(false);
self.Remind = ko.observable(new RemindModel());
self.LoadUserInformation = function () {
$.post('/User/GetUserInfoForDashboardHeader',
function (response) {
InitTawkChat(response);
self.Header(new HeaderModel(response));
if ($('#accountId').length > 0) {
$('#accountId').html(response.accountId);
}
}, "json").done(function () { console.warn("loaderOff"); });
};
self.GetRemindProfile = function () {
self.CanShowRemindProfile(false);
$.post('/User/GetRemindProfile', function (result) {
if (result) {
self.CanShowRemindProfile(true);
self.Remind(new RemindModel(result));
}
});
};
self.GetMessages = function () {
$.post('/Messages/GetAll', {
page: 1,
pageSize: 4
}, function (result) {
var notifications = [];
_.map(result.Notifications, function (item) {
notifications.push(new MessageModel(item));
});
self.Messages(notifications);
self.UnSeenMessagesCount(result.UnseenNotifications);
});
};
Remove !important from display property in your css and let knockout inline handle display.
function viewModel(){
var self = this;
self.UnSeenMessagesCount = ko.observable();
self.initData = function(){
//dummy setTimeout for your ajax get.
setTimeout(function(){
self.UnSeenMessagesCount(4);
},1000);
}
}
var vm = new viewModel();
vm.initData();
ko.applyBindings(vm);
.unseen-notification-show {
content: '';
display: block;
position: absolute;
width: 17px;
height: 17px;
border: 1px solid #ffffff;
background-color: #8cdb16;
border-radius: 8px;
cursor: pointer;
z-index: 3;
color: white;
text-align: center;
line-height: 15px;
font-size: 11px;
font-family: 'Times New Roman', Times, serif;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
<div class="bell">
<div class="unseen-notification-show" data-bind="visible: UnSeenMessagesCount() > 0, text: UnSeenMessagesCount()" style="display:none"></div>
</div>
Sounds like some loading issues. Try moving your css from being loaded in the top of the HTML, to be loaded in the bottom/footer.
What you want to do, is to hide the circle until the result is loaded (either 0 or 1,2,3,4.. and so on. Depending on the number of notifications).
In your div you got this line style="display:none"> which hides the circle. Thats good!
Now you should make sure that the style for .unseen-notification-show which contains display: block !important; that shows the circle - Should not be run before the calculation of the number to show is done.
One way could be to place the file that loads your css to the bottom of the HTML (like moving your <link rel="stylesheet" href="test.css" />). Or another way is to only use css for the hiding and then use javascript/jQuery for showing the cirle.
If this didn't help - then please provide the code you use to generate the number.
I think the issue is due to
self.UnSeenMessagesCount = ko.observable(0);
so when your modal is getting created it is initialized with value 0. So when you refresh the page initially it is 0 but when self.getMessage is called it updates your value.

Javascript Slider with Multiple Allowed sections/timeslots

I want to implement a time-slot selector in jquery/javascript slider form.
There are a few slider libraries out there such as Ion Slider, jQRangeSlider etc. but I don't know how I would be going about this. It doesn't look like they support multiple "dead-zones".
I want the user to be able to select a timeslot (from and to) during a particular day. To select the day, I have implemented a date picker, then for the date, I retrieve the already occupied slots for instance:
07h00 - Available
07h30 - Available
08h00 - Occupied
08h30 - Occupied
09h00 - Occupied
09h30 - Available
...
18h30 - Available
19h00 - Available
So the range picker must look like this:
The user should only be able to select a time zone in the available sections (blue) and drag the start slider between the "available" section, and the end selector will move along with it. There might be multiple unavailable zones (Red).
Is this possible with the libraries already out there or is this a case of roll my own?
I have thought about using a bunch of check boxes then check all the boxes between the start and end time-slots, and disable the already occupied slots, but I think a slider like this would be much more user friendly to use, functionally and visually.
A double slider can be made with very little effort by overlaying two sliders on top of each other using CSS. You need to listen to the onchange events of these two and reset the slider to previous value or closet non dead region when set to a dead region.
var deadZones = [[2,3], [6,7]];
function showVal(input) {
deadZones.forEach(([from, to]) => {
// reset to old value if in dead zone
if(from <= input.value && input.value <= to)
input.value = input.oldValue;
});
input.oldValue = input.value;
//console.log(input.id, input.value);
displayValues();
}
function displayValues() {
var a = $('#a').val();
var b = $('#b').val();
$('#slider-values').text(`Min: ${Math.min(a,b)} Max: ${Math.max(a,b)}`);
}
displayValues();
html,body{
margin: 0; padding: 0;
}
#a, #b{
position: absolute;
top: 30px;
display: block;
z-index: 100;
}
#b {
top: 60px;
}
/* from: https://css-tricks.com/styling-cross-browser-compatible-range-inputs-css/ */
input[type=range] {
-webkit-appearance: none; /* Hides the slider so that custom slider can be made */
width: 90%; /* Specific width is required for Firefox. */
background: transparent; /* Otherwise white in Chrome */
margin-left: 5%;
}
input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none;
border: 1px solid #000000;
height: 36px;
width: 16px;
border-radius: 3px;
background: #ffffff;
cursor: pointer;
automatic */
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; /* Add cool effects to your sliders! */
position: relative;
}
input[type=range]#a::-webkit-slider-thumb {
top: 100px;
}
input[type=range]#b::-webkit-slider-thumb {
top: 70px;
}
.slider-bg {
width: 100%;
margin: 0; padding: 0;
margin-left: 2.5%;
position: relative;
z-index: 1;
top: 135px;
}
.slider-bg div {
display: inline-block;
width: 9%;
margin: 0; padding: 0;
text-align: center;
border-top: 1px solid green;
padding-top: 20px;
}
.slider-bg div.disabled {
border-top: 1px solid red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input id="a" type="range" min="1" max="10" value="1" oninput="showVal(this)" onchange="showVal(this)" />
<input id="b" type="range" min="1" max="10" value="9" oninput="showVal(this)" onchange="showVal(this)"/>
<hr />
<div class="slider-bg">
<div>1</div>
<div class="disabled">2</div>
<div class="disabled">3</div>
<div>4</div>
<div>5</div>
<div class="disabled">6</div>
<div class="disabled">7</div>
<div>8</div>
<div>9</div>
<div>10</div>
</div>
<div id="slider-values"></div>
I have decided to implement an ionRangeSlider with custom slots from 05h30 to 19h30. A separate array of used time-slots to which I compare in the onChange event.
var slider = $("#timeslotSlider").ionRangeSlider({
type: "double",
grid: true,
from: 1,
from_value: "06h00",
to: 2,
to_value: "06h30",
values: timeslotvalues,
onChange: function (data) {
timeslotSetSelectedText(data);
}
});
var sliderdata = slider.data("ionRangeSlider");
var dt = sliderdata.result.from_value != null ? sliderdata.result : sliderdata.options;
timeslotSetSelectedText(dt);
The timeslotSetSelectedText function compares the selected range to the used slots then display a message "Available" or "Overlaps Existing time-slot"
The same function is used to Validate the selected slot before sending it to the server.

AppendTo but each new append is unique but still uses the same jquery

I'm wondering if it's possible to on each appendTo make the new div unique but still use the same jquery.
As you can see in the mark-up below, each new div shares the same jquery so doesn't work independently.
Within my Javascript i'm selecting the ID to fire each function.
I've tried just adding + 1 etc to the end of each ID, but with that it changes the name of the ID making the new created DIV not function.
I've thought of using DataAttribues, but i'd still have the same issue having to create multiple functions all doing the same job.
Any ideas?
Thanks
$(function() {
var test = $('#p_test');
var i = $('#p_test .upl_drop').length + 1;
$('#addtest').on('click', function() {
$('<div class="file-input"><div class="input-file-container upl_drop"><label for="p_test" class="input-file-trigger">Select a file...<input type="file" id="p_test" name="p_test_' + i + '" value=""class="input-file"></label></div><span class="remtest">Remove</span><p class="file-return"></p></div>').appendTo(test);
i++;
});
$('body').on('click', '.remtest', function(e) {
if (i > 2) {
$(this).closest('.file-input').remove();
i--;
}
});
});
var input = document.getElementById( 'file-upload' );
var infoArea = document.getElementById( 'file-upload-filename' );
input.addEventListener( 'change', showFileName );
function showFileName( event ) {
// the change event gives us the input it occurred in
var input = event.srcElement;
// the input has an array of files in the `files` property, each one has a name that you can use. We're just using the name here.
var fileName = input.files[0].name;
// use fileName however fits your app best, i.e. add it into a div
textContent = 'File name: ' + fileName;
$("#input-file-trigger").text(function () {
return $(this).text().replace("Select a file...", textContent);
});
}
/*
#### Drag & Drop Box ####
*/
.p_test{
display: inline-block;
}
.upl_drop{
border: 2px dashed #000;
margin: 0px 0px 15px 0px;
}
.btn--add p{
cursor: pointer;
}
.input-file-container {
position: relative;
width: auto;
}
.input-file-trigger {
display: block;
padding: 14px 45px;
background: #ffffff;
color: #1899cd;
font-size: 1em;
cursor: pointer;
}
.input-file {
position: absolute;
top: 0; left: 0;
width: 225px;
opacity: 0;
padding: 14px 0;
cursor: pointer;
}
.input-file:hover + .input-file-trigger,
.input-file:focus + .input-file-trigger,
.input-file-trigger:hover,
.input-file-trigger:focus {
background: #1899cd;
color: #ffffff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="p_test" id="p_test">
<div class="file-input">
<div class="input-file-container upl_drop">
<input class="input-file" id="file-upload" type="file">
<label tabindex="0" for="file-upload" id="input-file-trigger" class="input-file-trigger">Select a file...</label>
</div>
<div id="file-upload-filename"></div>
</div>
<button class="btn--add" id="addtest">
Add
</button>
</div>
I'd advise against using incremental id attributes. They become a pain to maintain and also make the logic much more complicated than it needs to be.
The better alternative is to use common classes along with DOM traversal to relate the elements to each other, based on the one which raised any given event.
In your case, you can use closest() to get the parent .file-input container, then find() any element within that by its class. Something like this:
$(function() {
var $test = $('#p_test');
$('#addtest').on('click', function() {
var $lastGroup = $test.find('.file-input:last');
var $clone = $lastGroup.clone();
$clone.find('.input-file-trigger').text('Select a file...');
$clone.insertAfter($lastGroup);
});
$test.on('click', '.remtest', function(e) {
if ($('.file-input').length > 1)
$(this).closest('.file-input').remove();
}).on('change', '.input-file', function(e) {
if (!this.files)
return;
var $container = $(this).closest('.file-input');
$container.find(".input-file-trigger").text('File name: ' + this.files[0].name);
});
});
.p_test {
display: inline-block;
}
.upl_drop {
border: 2px dashed #000;
margin: 0px 0px 15px 0px;
}
.btn--add p {
cursor: pointer;
}
.input-file-container {
position: relative;
width: auto;
}
.input-file-trigger {
display: block;
padding: 14px 45px;
background: #ffffff;
color: #1899cd;
font-size: 1em;
cursor: pointer;
}
.input-file {
position: absolute;
top: 0;
left: 0;
width: 225px;
opacity: 0;
padding: 14px 0;
cursor: pointer;
}
.input-file:hover+.input-file-trigger,
.input-file:focus+.input-file-trigger,
.input-file-trigger:hover,
.input-file-trigger:focus {
background: #1899cd;
color: #ffffff;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<div class="p_test" id="p_test">
<div class="file-input">
<div class="input-file-container upl_drop">
<input class="input-file" type="file">
<label tabindex="0" for="file-upload" class="input-file-trigger">Select a file...</label>
</div>
<div class="file-upload-filename"></div>
</div>
<button class="btn--add" id="addtest">Add</button>
</div>
Note that I've made a couple of other optimisations to the code. Firstly it now makes a clone() of the last available .file-input container when the Add button is clicked. This is preferred over writing the HTML in the JS file as it keeps the two completely separate. For example, if you need to update the UI, you don't need to worry about updating the JS now, as long as the classes remain the same.
Also note that you were originally mixing plain JS and jQuery event handlers. It's best to use one or the other. As you've already included jQuery in the page, I used that as it makes the code easier to write and more succinct.
Finally, note that you didn't need to provide a function to text() as you're completely over-writing the existing value. Just providing the new string is fine.

Using jQuery to alternate between classes upon click event

I'm feeling awfully silly here - I can't get a simple class switching statement to work in jQuery! I can only sit in frustration as for the last 45 minutes, I've searched Stack Overflow questions and answers, to no avail.
My goal is, upon clicking an item with the colorClick id (already containing a default class of "white"), to rotate that item between being assigned the class green, yellow, orange, red, and back to white again (ad infinitum).
The CSS is simple - each class simply corresponds to a different background color.
The HTML is simple - a div tag with two CSS classes (one static, one to be changed by jQuery).
The jQuery is simple - read the class on the clicked item, and change it.
And now, you understand what vexes me. Here's what I'm working with so far:
$("#colorClick").click(function () {
if ($(this).hasClass('white')) {
$(this).removeClass("white").addClass("green");
} else if ($(this).hasClass('green')) {
$(this).removeClass('green').addClass('yellow');
} else if ($(this).hasClass('yellow')) {
$(this).removeClass('yellow').addClass('orange');
} else if ($(this).hasClass('orange')) {
$(this).removeClass('orange').addClass('red');
} else if ($(this).hasClass('red')) {
$(this).removeClass('red').addClass('white');
});
.toDoItem {
text-align: left;
padding: 3px;
margin-bottom: 5px;
border: 1px solid;
border-color: #e8e7e7;
}
.white {
background-color: #ffffff;
}
.green {
background-color: #b2d8b2;
}
.yellow {
background-color: #ffffb2;
}
.orange {
background-color: #ffe4b2;
}
.red {
background-color: #ffb2b2;
}
<div class="toDoItem white" id="colorClick">To-do list item</div>
<div class="toDoItem white" id="colorClick">To-do list item</div>
<div class="toDoItem white" id="colorClick">To-do list item</div>
Link to the fiddle:
http://jsfiddle.net/andrewcbailey89/4Lbm99v0/2/
First things first, when making a list, you should use the correct list elements. Your "To Do" list fits the definition of a description list (<dl>) so you should use that instead of <div> elements.
You can save a lot of lines of code by getting rid of the classes and creating an array of colors. Make sure that the colors are in the same order that you want them to be shown. We will use this array to set the background color based on an incremented counter.
var colors = ['#b2d8b2', '#ffffb2', '#ffe4b2', '#ffb2b2', '#fff'];
You can also greatly simplify your script by using a "factory" function which defines a scope and builds an event listener function, which it returns. This creates a "safe" scope for each listener function to reside in that we can define variables which will store information between events.
In the following snippet, we define a count variable that we increment on each click. We use the incremented variables remainder when dividing by the length of the color array using the modulo operator %. If the number is smaller than the length of the array, it will return the number, otherwise it will return the remainder when dividing by the length of the array, allowing us to loop through continuously.
function todoItemListener() {
var count = 0;
return function () {
$(this).css({ 'background-color': colors[count++ % colors.length] });
}
}
Then instead of assigning the function declaration as normal (without the parenthesis), we assign the result of the factory function, simply append the parenthesis and the function will execute and return the resulting listener function. This allows us to add as many listener functions as we want, so if you're adding new todo list items, we can simply build another listener function.
$('.todo-list dd').each(function () {
$(this).on('click', todoItemListener());
});
$('.add-item').on('click', function () {
var list = this.parentNode.parentNode;
$('<dd>To-do list item</dd>').appendTo(list).on('click', todoItemListener());
});
This method also allows you to easily change the array of colors at will. So say if an option is selected somewhere on the page, another color could become available, or not available.
Also, for some extra UX goodness, I added CSS to stop selection of the text on click (that can get annoying) and to change the cursor to a pointer to give it a more actionable feel.
Here is the full demo, I've included multiple to-do lists to show that it can be done.
var colors = ['#b2d8b2', '#ffffb2', '#ffe4b2', '#ffb2b2', '#fff'];
function todoItemListener() {
var count = 0;
return function () {
$(this).css({ 'background-color': colors[count++ % colors.length] });
}
}
$('.todo-list dd').each(function () {
$(this).on('click', todoItemListener());
});
$('.add-item').on('click', function () {
var list = this.parentNode.parentNode;
$('<dd>To-do list item</dd>').appendTo(list).on('click', todoItemListener());
});
.glyphicon-plus-sign {
font-size: 15px;
}
.todo-list {
background: #efefef;
padding: 3px;
}
.todo-list dd {
margin: 0;
text-align: left;
padding: 3px;
margin-bottom: 7px;
border: 1px solid;
border-color: #e8e7e7;
background-color: #fff;
}
.add-item, .todo-list dd {
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
cursor: pointer;
}
.add-item {
float: right;
margin: 4px;
}
.todo-list dh::after {
content: "";
display: block;
clear: both;
}
.todo-list dh h3 {
float: left;
margin: 0px;
max-width: 100%;
overflow: hidden;
}
/* This rule is for the demo only */
.wrp {
float: left;
width: 33.33333333%;
padding: 1px;
}
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<div class="wrp">
<dl class="todo-list" id="todo-list-1">
<dh>
<h3 class="center" contenteditable>To Do List 1</h3>
<span class="add-item glyphicon glyphicon-plus-sign"></span>
</dh>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
</dl>
</div>
<div class="wrp">
<dl class="todo-list" id="todo-list-2">
<dh>
<h3 class="center">To Do List 2</h3>
<span class="add-item glyphicon glyphicon-plus-sign"></span>
</dh>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
</dl>
</div>
<div class="wrp">
<dl class="todo-list" id="todo-list-3">
<dh>
<h3 class="center">To Do List 3</h3>
<span class="add-item glyphicon glyphicon-plus-sign"></span>
</dh>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
<dd>To-do list item</dd>
</dl>
</div>
You are missing some quotes in a few places, and you didn't close the last if statement.
ex: $(this).hasClass(green) should be $(this).hasClass('green')
Additionally, you should change colorClick to a class rather than an ID, as there are multiple of these elements.
I also changed all of your quotes to single quotes for consistency's sake.
Here is a working snippet:
$(".colorClick").click(function () {
if ($(this).hasClass('white')) {
$(this).removeClass('white').addClass('green');
} else if ($(this).hasClass('green')) {
$(this).removeClass('green').addClass('yellow');
} else if ($(this).hasClass('yellow')) {
$(this).removeClass('yellow').addClass('orange');
} else if ($(this).hasClass('orange')) {
$(this).removeClass('orange').addClass('red');
} else if ($(this).hasClass('red')) {
$(this).removeClass('red').addClass('white');
}
});
.toDoItem {
text-align: left;
padding: 3px;
margin-bottom: 5px;
border: 1px solid;
border-color: #e8e7e7;
}
.white {
background-color: #ffffff;
}
.green {
background-color: #b2d8b2;
}
.yellow {
background-color: #ffffb2;
}
.orange {
background-color: #ffe4b2;
}
.red {
background-color: #ffb2b2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="colorClick toDoItem white">To-do list item</div>
<div class="colorClick toDoItem white">To-do list item</div>
<div class="colorClick toDoItem white">To-do list item</div>
First you are using same id for multiple elements. id should be unique for each element. You can use toDoItem class instead of colorClick id to bind click event. To get rid of complex if else statement you can put all class in an array in your required sequence. Then on click of toDoItem change class according to the sequence of array. If you reached at the last item of array then go back to first.
var colors = ['white', 'green', 'yellow', 'orange', 'red'];
var total = colors.length-1;
$(".toDoItem").click(function() {
var color = $(this).attr('class').split(' ')[1];
var index = colors.indexOf(color);
index = index==total? 0 : index+1;
$(this).removeClass(color).addClass(colors[index]);
});
.toDoItem {
text-align: left;
padding: 3px;
margin-bottom: 5px;
border: 1px solid;
border-color: #e8e7e7;
}
.white {
background-color: #ffffff;
}
.green {
background-color: #b2d8b2;
}
.yellow {
background-color: #ffffb2;
}
.orange {
background-color: #ffe4b2;
}
.red {
background-color: #ffb2b2;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="toDoItem white">To-do list item</div>
<div class="toDoItem white">To-do list item</div>
<div class="toDoItem white">To-do list item</div>
JS FIDDLE DEMO

Categories

Resources