How to hide the dropdown when clicked outside the container - javascript

I have a International country code selector code for my website but the problem I'm facing is that I want to hide the dropdown menu when anyone clicks outside the container, I have already written the code for this feature but the bug I'm facing right now is that It closes the dropdown menu when anyone clicks inside of the container, not the outside. it's doing the exact opposite.
I want to hide the menu when clicked outside.
Please check out my Highlighted code -
// Cache the elements
const xbutton = document.querySelector('.country-code-selector .telcode');
const container = document.querySelector('.country-code-selector .container');
const input = document.querySelector('.country-code-selector input');
const list = document.querySelector('.country-code-selector .list');
// Add event listeners to the button, input, and list
// We use a process called "event delegation" on the list
// to catch events from its children as they "bubble up" the DOM
// https://dmitripavlutin.com/javascript-event-delegation/
xbutton.addEventListener('click', handleButton);
input.addEventListener('input', handleInput);
list.addEventListener('click', handleListClick);
document.addEventListener('click', handleDocumentClick);
// Handles the document click - it checks to see if the clicked
// part of the document has a parent element which is either
// `null` or is the HTML element, and then closes the container
// if it's open
function handleDocumentClick(e) {
const { parentElement } = e.target;
}
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
document.addEventListener("click", (event) => {
if (container.contains(event.target)) {
if (container.classList.contains('show')) {
container.classList.remove('show');
}
}
});
/************************************************************************/
/****************** Hide the menu when clicked outside ******************/
/************************************************************************/
// All of the data held as objects within an array
const data = [
{ name: 'Afganistan', code: '69', flag: 'afg' },
{ name: 'Barbados', code: '1-246', flag: 'brb' },
{ name: 'Bolivia', code: '591', flag: 'bol' },
{ name: 'Cuba', code: '53', flag: 'cub' },
{ name: 'Fiji', code: '679', flag: 'fji' },
];
// Filters the data based on the characters
// at the start of the provided name
function filterData(data, value) {
return data.filter(obj => {
return (
obj.name.toLowerCase().startsWith(value.toLowerCase())
|| obj.code.toLowerCase().startsWith(value.toLowerCase())
);
});
}
// Create a series of list items based on the
// data passed to it
function createListHtml(data) {
return data.map(obj => {
const { name, code, flag } = obj;
return `
<li
class="item"
data-name="${name}"
data-code="${code}"
data-flag="${flag}"
>
<div class="flag-icon flag-icon-${flag}"></div>
<div class="name">${name} (+${code})</div>
</li>
`;
}).join('');
}
// Toggle the container on/off
function handleButton() {
container.classList.toggle('show');
}
// No data available list item
function createNoDataHtml() {
return '<li class="nodata">No data available</li>';
}
// When the input is changed filter the data
// according to the current value, and then
// create some list items using that filtered data
function handleInput(e) {
const { value } = e.target;
if (value) {
const filtered = filterData(data, value);
if (filtered.length) {
list.innerHTML = createListHtml(filtered);
} else {
list.innerHTML = createNoDataHtml();
}
} else {
list.innerHTML = createListHtml(data);
}
}
// Create some button HTML
function createButtonHtml(code, flag) {
return `
<div class="flag-icon flag-icon-${flag}"></div>
<div class="code">+${code}</div>
`;
}
// Updates the selected list by removing the `selected`
// class from all items, and then adding one to the clicked
// item
function updateSelected(list, item) {
const items = list.querySelectorAll('.item');
items.forEach(item => item.classList.remove('selected'));
item.classList.add('selected');
}
// When an item is clicked, grab the relevant data
// attributes, create the new button HTML, and then
// close the container
function handleListClick(e) {
const item = e.target.closest('li') || e.target;
if (item.classList.contains('item')) {
const { code, flag } = item.dataset;
xbutton.innerHTML = createButtonHtml(code, flag);
updateSelected(list, item);
container.classList.remove('show');
}
}
list.innerHTML = createListHtml(data);
.country-code-selector .telcode {
margin-bottom: 1em;
display: flex;
}
.country-code-selector .telcode div.code, .item div.name {
margin-left: 0.25em;
}
.country-code-selector .container {
display: none;
width: 350px;
border: 1px solid black;
}
.country-code-selector .show {
display: block;
}
.country-code-selector .list {
height: 100px;
list-style: none;
margin: 1em 0 0 0;
padding: 0;
overflow-y: scroll;
border: 1px soldi darkgray;
}
.country-code-selector .item {
display: flex;
padding: 0.25em;
border: 1px solid lightgray;
}
.country-code-selector .item:hover {
cursor: pointer;
}
.country-code-selector .item:hover, .item.selected {
background-color: lightyellow;
}
<link href="https://amitdutta.co.in/flag/css/flag-icon.css" rel="stylesheet"/>
<div class="country-code-selector">
<button type="button" class="telcode">Tel code</button>
<section class="container">
<input type="text" placeholder="Search for country" />
<ul class="list"></ul>
</section>
</div>

Related

How to make a “Mark as Read” with localStorage?

I’m trying to figure out how to keep “Mark as Read” as “Mark as Unread” even after refreshing the page. Vice versa too. How do I save the data with localStorage? So far, this is my code for “Mark as Read”:
function readunread() {
currentvalue = document.getElementById("readunread").value;
if(currentvalue == "Mark as Unread"){
document.getElementById("readunread").value = "Mark as Read";
} else{
document.getElementById("readunread").value = "Mark as Unread";
}
}
body {
background:black;
}
.button {
border: none;
color: white;
font-family: Corbel;
text-align: center;
text-decoration: none;
display: inline-block;
font-size: 16px;
background-color: black;
}
input[type=button] {
font-size: 20px;
font-family: Corbel;
text-decoration: none;
color: white;
border: none;
background: none;
cursor: pointer;
margin: 0;
padding: 0;
}
<input type = "button" value = "Mark as Read" id = "readunread" onclick = "readunread();">
I click the “Mark as Read” and it becomes “Mark as Unread.” But after refreshing the page, it goes back to “Mark as Read.” How do I avoid that?
In your scripts you'll need to change two things:
<script>
function readunread() {
currentvalue = document.getElementById("readunread").value;
if (currentvalue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Read";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Read");
} else {
document.getElementById("readunread").value = "Mark as Unread";
// 1. Update the localstorage
localStorage.setItem("readunread", "Mark as Unread");
}
}
</script>
<input
type="button"
value="Mark as Read"
id="readunread"
onclick="readunread();"
/>
<script>
// 2. Get the value from the local storage
function loadInitialValue() {
const localValue = localStorage.getItem("readunread");
console.log(localValue);
if (localValue == "Mark as Unread") {
document.getElementById("readunread").value = "Mark as Unread";
} else {
document.getElementById("readunread").value = "Mark as Read";
}
}
loadInitialValue(); // Make sure to call the function
</script>
In order to manage the read/unread state of items using localStorage as your persistent data store, you'll need to serialize your (un)read state as some kind of string to store as a value in the storage area (because localStorage only stores string values), and then deserialize the value when you retrieve it. JSON is an accessible choice for the serialization format because it naturally represents many JavaScript data structures and is easy to parse/stringify.
Questions like these are always hard to demonstrate in a working code snippet because Stack Overflow's code snippet environment is sandboxed and prevents access to things like localStorage, so when you try to use those features, a runtime exception is thrown as a result of the lack of permissions. Nonetheless...
Below I've provided a self-contained example of storing the read/unread state for a list of items using basic functional programming techniques to keep the code organized. This is just plain HTML + CSS + JavaScript and doesn't use any frameworks like React, etc. You can copy + paste the code into a local HTML file on your computer and then serve it using a local static file web server (e.g. with Deno or Python, etc.) to see it working. I've included verbose comments for you to explain what's happening at every step of the program.
If you want to examine the state of your localStorage as you test the demo, see the question How to view or edit localStorage?.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>LocalStorage: read/unread items</title>
<style>
/* Just some styles for this example: styling is up to you */
* { box-sizing: border-box; }
body { font-family: sans-serif; }
.toggle-status {
font-size: 1rem;
padding: 0.25rem;
width: 8rem;
}
#list {
list-style: none;
padding: 0;
display: flex;
flex-direction: column;
gap: 0.5rem;
align-items: flex-start;
}
.item {
display: flex;
gap: 1rem;
align-items: center;
}
.item.read > .item-content { font-weight: normal; }
.item.unread > .item-content { font-weight: bold; }
</style>
<script type="module">
// Get the state of all of the read/unread items from localStorage
// as an object:
function getStatusMap () {
try {
// Get the JSON value from local storage:
// if it doesn't exist, it will be null, so use a default value instead:
// a JSON string representing an empty object:
const storageValue = window.localStorage.getItem('read_status_map') ?? '{}';
// Parse the string value into an actual object:
const readStatusMap = JSON.parse(storageValue);
// Return the value if it's a plain object:
if (
typeof readStatusMap === 'object'
&& readStatusMap !== null
&& !Array.isArray(readStatusMap)
) return readStatusMap;
// Else throw an error because it was an invalid value:
throw new Error('Unepxected value');
}
catch (ex) {
// Catch any exception which might have occurred.
// You can handle it however you want (or just ignore it).
// For example, you could print it
// to the console error stream to view it:
console.error(ex);
// Return an empty object as the default:
return {};
}
}
// Update the localStorage state of all the read/unread items:
function setStatusMap (statusMap) {
const json = JSON.stringify(statusMap);
const storageValue = window.localStorage.setItem('read_status_map', json);
}
// Update the read/unread status for a single item:
function updateStatus (statusMap, listItemElement, isReadStatus) {
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Depending on the current status, update the action button's text
// to describe the next (opposite) action:
button.textContent = `Mark as ${isReadStatus ? 'unread' : 'read'}`;
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the state object of the current item from the status map object,
// OR create one if it doesn't exist yet. You can store other information
// about each item here, but — in this example — only the ID (string)
// and read status (boolean) properties are stored:
const status = statusMap[id] ??= {id, isRead: false};
// Update the read status of the item:
status.isRead = isReadStatus;
// Update the whole state in localStorage:
setStatusMap(statusMap);
// Optional: update the list item's read/unread class.
// This can help with applying CSS styles to the items:
if (isReadStatus) {
listItemElement.classList.add('read');
listItemElement.classList.remove('unread');
}
else {
listItemElement.classList.remove('read');
listItemElement.classList.add('unread');
}
}
// A convenience function which toggles between read/unread for an item:
function toggleStatus (statusMap, listItemElement) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Get the current status (or false by default if it doesn't exist yet):
let isRead = statusMap[id]?.isRead ?? false;
// Toggle it to the opposite state:
isRead = !isRead;
// Update it:
updateStatus(statusMap, listItemElement, isRead);
}
// Now, using the functions above together:
function main () {
// Get the initial read/unread status map:
const statusMap = getStatusMap();
// Get an array of the item elements:
const listItemElements = [...document.querySelectorAll('#list > li.item')];
for (const listItemElement of listItemElements) {
// Get the ID from the list item's data attribute:
const {id} = listItemElement.dataset;
// Set the initial read status for each item to what was found
// in localStorage, or if nothing was found then set to false by default:
const initialStatus = statusMap[id]?.isRead ?? false;
updateStatus(statusMap, listItemElement, initialStatus);
const button = listItemElement.querySelector(':scope > button.toggle-status');
// Set an action for each item's toggle button: when it is clicked,
// toggle the status for that item. Formally, this is called "binding an
// event listener callback to the button's click event":
button.addEventListener(
'click',
() => toggleStatus(statusMap, listItemElement),
);
}
}
// Invoke the main function:
main()
</script>
</head>
<body>
<!--
A list of items, each with:
- a unique ID
- a toggle button,
- and some text content
-->
<ul id="list">
<li class="item" data-id="cc9e88ce-3ed4-443a-84fc-fa7147baa025">
<button class="toggle-status">Mark as read</button>
<div class="item-content">First item content</div>
</li>
<li class="item" data-id="23a9204c-905f-48db-9f6a-deb3c8f82916">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Second item content</div>
</li>
<li class="item" data-id="18b47e4c-635f-49c0-924e-b9088538d08a">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Third item content</div>
</li>
<li class="item" data-id="ed2aacca-64f0-409d-8c1b-d1bdcb7c6058">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Fourth item content</div>
</li>
<li class="item" data-id="0fce307b-656a-4102-9dc9-5e5be17b068d">
<button class="toggle-status">Mark as read</button>
<div class="item-content">Fifth item content</div>
</li>
<!-- ...etc. -->
</ul>
</body>
</html>
You were really close. What you have left is to use the local storage. For that, replace your JavaScript by the code below:
// On Load
const readUnreadButton = document.getElementById("readunread");
document.getElementById("readunread").value =
localStorage.getItem("readunread") || readUnreadButton.value;
// On Click
function readunread() {
const readUnreadButton = document.getElementById("readunread");
currentvalue = readUnreadButton.value;
if (currentvalue == "Mark as Unread") {
readUnreadButton.value = "Mark as Read";
localStorage.setItem("readunread", "Mark as Read");
} else {
readUnreadButton.value = "Mark as Unread";
localStorage.setItem("readunread", "Mark as Unread");
}
}

Wrapped Img is not being picked up in JS

I've been stuck on this for a few days. I've tried different selectors and unwrapping the img in the div if that was the problem but no luck. I've been trying to make an accordion.
I'm trying to add a class of "rotate" to the img with the class of "arrow". So that when the question tag is clicked, the arrow img will also rotate.
const questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
});
.question + .answer {
display: none;
overflow: hidden;
transition: all ease 1s;
}
.question.open + .answer {
display: block;
}
.arrow.rotate {
transform: rotate(180deg);
}
<div class="container">
<div class="question">How many team members can I invite?
<img class="arrow" src="./images/icon-arrow-down.svg">
</div>
<div class="answer">You can invite up to 2 additional users on the Free plan. There is no limit on
team members for the Premium plan.</div>
</div>
You're missing [0] in your code.
The arrowTag comes from document.querySelectorAll(), which returns a NodeList, you need to specify the element from that NodeList:
var questionTag = document.querySelectorAll('.question')
questionTag.forEach(questionTag => {
questionTag.addEventListener('click', () => {
if (questionTag.classList.contains('open')) {
questionTag.classList.remove('open');
} else {
const questionTagOpen = document.querySelectorAll('.open');
questionTagOpen.forEach((questionTagOpen) => {
questionTagOpen.classList.remove('open');
});
questionTag.classList.add('open');
}
});
var arrowTag = document.querySelectorAll('img.arrow')
questionTag.addEventListener('click', () => {
arrowTag[0].classList.toggle('rotate'); // missing [0] added here
});
});
The addEventListener function is applied to an event target.
Thus, you cannot apply it to the NodeList, which is stored in your arrowTag:

Text is not appearing after the second insert

these code is working except that for the second or subsequent insert, the message 'Order added' is not appearing. Secondly, after I removed everything using pop and click to view current order, the message 'No customer orders at the moment.' is not appearing even though the code executed.
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeTo(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeTo(4000, 0);
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeTo(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})
html{
font-size:1em
}
div.Mesg {
height: 20px !important;
}
ul#menu {
display: flex;
list-style-type: none;
justify-content: space-around;
padding: 0;
margin: 0;
}
ul#menu > li {
display: block;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<ul id="menu">
<li><a id="ViewAllOrders" href="#">View all orders</a></li>
<li><a id="ViewCurrentOrder" href="#">View current order in process</a></li>
<li><a id="DeliverOrder" href="#">Deliver one order</a></li>
</ul>
<hr />
<div class="Mesg"></div>
<hr />
<div id="AddOrder">
<input id="customerName" type="text" placeholder="Enter customer's name" />
<input id="btnAdd" type="button" value="Add" />
</div>
<div id="AllOrders"></div>
<div id="CurentOrder"></div>
Is it due to fadeTo method?
Change fadeTo() to fadeOut() should fix this
const order = [];
const customer = {
name: '',
totalCups: 0
}
const checkCustomerOrders = () => {
if (order.length === 0) {
$('.Mesg').show();
$('.Mesg').text("No customer orders at the moment.").fadeOut(4000, 0);
}
}
$('#AllOrders').hide();
$('#btnAdd').click(function () {
var item = $('#customerName');
// Data structure Queue
order.unshift(item.val());
// UX
$('.Mesg').text("Order added").fadeOut(4000, 0, function(){
$('.Mesg').text('');
$('.Mesg').show();
});
// UI
var orderElement = $('<div class="orderItem"></div>').text(item.val());
$('#AllOrders').append(orderElement);
// Reset textbox
item.val("");
// Optional Design
$('#ViewAllOrders').click();
debugger;
})
$('#ViewAllOrders').click(function () {
checkCustomerOrders();
$('#AllOrders').show();
$('#CurentOrder').hide();
})
$('#ViewCurrentOrder').click(function () {
debugger;
checkCustomerOrders();
$('#AllOrders').hide();
$('#CurentOrder').show();
var top = order[order.length - 1];
console.log(top);
$('#CurentOrder').empty();
// UI
var orderElement = $('<div></div>').text(top);
$('#CurentOrder').append(orderElement);
})
$('#DeliverOrder').click(function () {
debugger
// Remove one element from array. FIFO.
order.pop();
// Element removed.
// Remove the html element as well
$(".orderItem:first").remove();
// UX
$('.Mesg').text("One customer order delivered").fadeOut(4000, 0);
// Optional Design
$('#ViewAllOrders').click();
})

How to make each <i> element responsive to it's parent input created on 'keypress'

I'm coding a To-Do List in Vanilla Javascript and I want to make it create a new input every time someone presses return on the previous one, which for now is working. I also want to append a checkmark to it, so whenever the task has been finished you can click on it, and it will change the background-color of the input next to which the checkmark is. The only problem is I don't know how to assign some kind of value to each checkmark so the eventListener doesn't always get the first ID selected.
Tried assigning a value to each checkmark and put it into an array, but do not know how to actually assign the exact same value of each checkmark into the array.
let counter, checkmark, cross, list, newRow, addInput, addCheckmark, listid, wrapper, current1;
counter = 1;
checkmark = document.getElementById('checkmark');
cross = document.getElementById('cross');
wrapper = document.querySelector('.to-do-wrapper');
current1 = document.getElementById('current1');
let values = [];
// Event Delegation to listen to all target.matches :)
document.addEventListener('keypress', function(e) {
if (event.target.matches('.input-new-list')) {
let randomVal = Math.floor(Math.random() * 100000);
list = document.querySelector('.list');
newRow = document.createElement("li");
addInput = document.createElement('input');
addCheckmark = document.createElement('i');
addCheckmark.setAttribute('class', 'fas fa-check');
addInput.setAttribute('id', 'current-' + counter)
addInput.setAttribute('class', 'input-new-list');
addInput.setAttribute('type', 'text');
newRow.setAttribute('class', 'new-list');
let key = e.keyCode;
if (key === 13) {
list.appendChild(newRow);
newRow.appendChild(addCheckmark);
addCheckmark.setAttribute('id', 'checkmark');
/* addCheckmark.setAttribute('value', randomVal);
values.push(randomVal); */
newRow.appendChild(addInput);
document.getElementById('current-' + counter).focus();
counter++;
document.querySelector('#default').removeAttribute('placeholder');
}
}
});
// Show edit buttons on click edit list
document.addEventListener('click', function(event) {
list = document.querySelector('.list');
newRow = document.createElement("li");
addInput = document.createElement('input');
addCheckmark = document.createElement('i');
// Ad a random value to checkmark -> Push into array. If event.target.matches checkmark value => execute if
if (event.target.matches('#checkmark')) {
// On click of checkmark, change input background and toggle checkmark color
if (event.target.classList !== 'active') {
checkmark.classList.toggle('active');
if (document.querySelector('.input-new-list')) {
document.querySelector('.input-new-list').classList.toggle('checked');
} else if (document.querySelector('current' + counter)) {
document.querySelector('#current' + counter).classList.toggle('checked')
}
}
}
});
document.addEventListener('mouseover', function() {
if (event.target.matches('input')) {
cross.classList.add('active');
} else if (!event.target.matches('input')) {
cross.classList.remove('active');
}
});
<div class="container-fluid to-do-wrapper" id="toDo">
<ul class="list">
<li class="new-list">
<i class="fas fa-check" id="checkmark"></i>
<input type="text" placeholder="Create a new list" class="input-new-list" id="default" />
<i class="fas fa-times" id="cross"></i>
</li>
</ul>
</div>
Just looking for a way to assign each checkmark and input to its parent li, so doing something on it, wouldn't affect the first selected element but the one being edited.
I kind of went a different direction, storing the data as information in the DOM, and providing a function to generate a JS object that represents todo list data on demand. I used Kamil's CSS in my answer, with some slight changes.
const list = document.querySelector('ul.list');
document.addEventListener('keypress', function(e) {
const eventTarget = e.target;
if (e.keyCode === 13 && eventTarget.parentElement && eventTarget.parentElement.classList.contains('new-item')) {
const clonedListItem = eventTarget.parentElement.cloneNode(true);
clonedListItem.classList.remove('new-item');
const icons = clonedListItem.querySelectorAll('i');
icons.forEach(el => el.classList.remove('hidden'));
const [doneIcon, deleteIcon] = icons;
doneIcon.addEventListener('click', () => toggleDone(clonedListItem));
deleteIcon.addEventListener('click', () => deleteItem(clonedListItem));
list.insertBefore(clonedListItem, eventTarget.parentElement);
eventTarget.removeAttribute('placeholder');
eventTarget.value = '';
}
});
document.getElementById('generateJSON').addEventListener('click', () => {
const data = [...document.querySelectorAll('ul.list li')]
.filter(li => !li.classList.contains('new-item'))
.map(li => ({
text: li.querySelector('input[type="text"]').value,
done: li.classList.contains('done')
}));
console.log('data', data);
});
function toggleDone(item) {
item.classList.toggle('done');
}
function deleteItem(item) {
item.remove();
}
ul {
list-style-type: none;
}
.fas {
cursor: pointer
}
li.done input[type="text"] {
background: #dfd
}
.input-new-list {
margin: 5px
}
li i.fa-check::after {
content: '[+]'
}
li.done i.fa-check::after {
content: '[-]'
}
.hidden {
display: none;
}
<ul class="list">
<li class="new-item">
<i class="fas fa-check hidden"></i>
<input type="text" placeholder="Create a new list" />
<i class="fas hidden" id="cross">x</i>
</li>
</ul>
<input type="button" id="generateJSON" value="Generate JSON" />
You can 'assign value' to checkmark by add/remove class. Don't use same id for many elements. Try to change approach and separate view and data using <template> as follows
let list= [{text:'', done: false}]; // data
function show() {
toDo.innerHTML = list.map((x,i)=> inject(item.innerHTML,{
check: x.done ? 'fa-check' : 'fa-uncheck',
done: x.done ? 'done' : '',
hide: i ? '' : 'hide',
text: x.text,
i,
})).join('');
}
function inject(str, obj) { return str.replace(/\${(.*?)}/g, (x,g)=> obj[g]) }
function check(i) { list[i].done = !list[i].done; show(); }
function change(inp,i) { list[i].text = inp.value; }
function del(i) { list.splice(i,1); show(); }
function newItem(i) {
list.splice(i+1,0,{text:'',done: false});
show();
this['inp'+(i+1)].focus();
}
show();
ul{ list-style-type: none; }
.fas { cursor: pointer }
.done { background: #dfd }
.input-new-list {margin: 5px}
.fa-check::after { content: '[+]'}
.fa-uncheck::after { content: '[-]'}
.hide { display: none }
Click on left '[-]' to mark task as done, type enter to add new, click 'x' to delete
<div class="container-fluid to-do-wrapper">
<ul class="list" id="toDo"></ul>
</div>
<template id="item">
<li class="new-list">
<i class="fas ${check}"
onclick="check(${i})"></i>
<input type="text" placeholder="Create a new list"
class="input-new-list ${done}"
value="${text}" id="inp${i}"
onchange="newItem(${i})"
oninput="change(this, ${i})"/>
<i class="fas fa-times ${hide}" onclick="del(${i})">x</i>
</li>
</template>

Styling Polymer Element Dynamically

I am trying to make an paper-card element change colors based on the status of the customers data on Fire base, but for some reason the color only updates on the second click of the customer. Right now I have the paper cards ID set to the firebase data in order to make it change colors. Here's my elements style code:
<style is="custom-style">
:host {
display: block;
}
#cards {
#apply(--layout-vertical);
#apply(--center-justified);
}
.row {
padding: 20px;
margin-left: 10px;
}
paper-card {
padding: 20px;
}
#check {
float: right;
bottom: 15px;
--paper-card
}
#Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
#Default {
/*Apply Default Style*/
/*--paper-card-content: {*/
/* background: var(--paper-red-500);*/
/*};*/
}
paper-icon-button.check{
color: var(--paper-green-500);
}
paper-icon-button.check:hover{
background: var(--paper-green-50);
border-radius: 50%;
}
#check::shadow #ripple {
color: green;
opacity: 100%;
}
.iron-selected{
color: green;
}
And here is the template:
<template>
<firebase-collection
location="https://calllistmanager.firebaseio.com/Wilson"
data="{{wilsonData}}"></firebase-collection>
<div id="cards">
<template id="cards" is="dom-repeat" items="{{wilsonData}}" as="customer">
<paper-card id="{{customer.status}}" class="{{customer.status}}" heading="[[customer.__firebaseKey__]]">
<div class="card-content">
<span>Phone: </span><span>[[customer.number]]</span>
<span>Status: </span><span>[[customer.status]]</span>
<paper-icon-button style="color: green" id="check" on-tap="checktap" icon="check">
</paper-icon-button>
</div>
</paper-card>
</template>
</div>
Here is my script:
<script>
(function() {
Polymer({
is: 'list-display',
properties: {
wilsonData: {
type: Object,
observer: '_dataObserver'
}
},
ready: function() {
var listRef = new Firebase("https://calllistmanager.firebaseio.com/Wilson");
},
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var stat;
var store = ref.child(e.model.customer.__firebaseKey__);
store.on("value", function(snapshot){
stat = snapshot.child("status").val();
});
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.customer.status = "Default";
}
else {
store.update({
"status": "Done"
});
e.model.customer.status = "Done";
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles()
}
});
})();
at first I thought the problem may be that the function runs updateStyles(); faster than firebase can update but it always works fine on the second click...any suggestions?
I think the problem could be caused by the call to firebase. store.on("value", is not a synchronous function. However, later in your code you assume that you already have a value, that will be set later on whenever the value event fires. You could try adding the rest of your code in the event handler. Like this:
checktap: function(e){
// e.model.customer.status = "Done";
console.log("Starting Status: " + e.model.customer.status);
ref = new Firebase("https://calllistmanager.firebaseio.com/Wilson")
var store = ref.child(e.model.customer.__firebaseKey__);
store.once("value", function(snapshot){
var stat = snapshot.child("status").val();
if(stat == "Done"){
store.update({
"status": "Default"
});
e.model.set("customer.status", "Default");
}
else {
store.update({
"status": "Done"
});
e.model.set("customer.status", "Done");
}
console.log("Ending Status: " + e.model.customer.status);
this.updateStyles();
}.bind(this));
}
Essentially, you wait until the stat variable has been set to do the rest of your tasks. Also note, the bind(this) at the end, which will allow you to update the the styles from the event handler.
Update
There are a couple of more issues. First it's better to uses classes for changing the styles and not IDs. IDs should not change. Then, to bind to the class attribute, use the $ sign. When you update the model, you should use the set API.
Have a look at this plunker. It is a small working example (only works in Chrome) that changes styles when you click the checkmark. It does not use Firebase, however.
Here's how you could to the style with classes.
.Done {
--paper-card-header: {
background: var(--paper-green-500);
};
--paper-card-content: {
background: var(--paper-green-300);
};
}
And in your template:
<paper-card class$="{{customer.status}}" heading="[[customer.__firebaseKey__]]">

Categories