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");
}
}
Related
this is written in JS
i cant seem to make the MovieDetails button work at all.
function searchMovie(query) {
const url = `https://imdb8.p.rapidapi.com/auto-complete?q=${query}`;
fetch(url, options)
.then(response => response.json())
.then(data => {
const list = data.d;
list.map((item) => { //makes a list of each individual movie from the data
const name = item.l; // holds the name of movie
const poster = item.i.imageUrl; // holds the poster, given by the data
const detail = item.id // holds the ttid of the movie
// below is what shows the poster, movie name, etc
const movie =
`
<body>
<div class="colmd3">
<div class = "well text-center">
<li><img src="${poster}">
<h2>${name}</h2>
</li>
<button type = "button" id = "MovieDetails" class="btn btn-primary" href="#">Movie Details</button>
<script>
document.getElementById('MovieDetails').addEventListener("click",myFunction);
function myFunction(){
console.log(detail)
}
</script>
</div>
</div>
</body>
`;
document.querySelector('.movies').innerHTML += movie; // returns the first element movies and poster to movie div
//console.log()
});
document.getElementById("errorMessage").innerHTML = "";
})
.catch((error) => {
document.getElementById("errorMessage").innerHTML = error;
});
// we should make a condition here for when a new item is placed here there will be a page refresh
// setTimeout(() => {
// location.reload(); }, 2000);
}
the function above will make an api call and then save the results into list, i can hold specific elements of the list in the three const's and const movie will output the movie poster, name and display it.
I want to make a button for each movie that when clicked will output the id of the movie which is held in const details.
But i cant figure out how to make it work, i have tried (button onclick = function ..) and (document.getElementById...) but it says that getElementById cant be null.
i know that this seems like a silly problem but i cant seem to figure how to make the button actually output something useful or any other way to make a button be mapped out to each api call.
You're heading in the right direction but there are a couple of pain-points with your code as the other commenters have indicated.
Your template string is adding a brand new body element to the page for each movie where there should just be one for the whole document. Nice idea to use a template string though - by far the simplest method to get new HTML on to the page.
Adding JS to the page dynamically like that is going to end up causing you all kinds of problems - probably too many to mention here, so I'll just skip to the good part.
First remove the body element from the template string, and perhaps tidy up the remaining HTML to be a little more semantic. I've used section here but, really, anything other than having lots of divs is a step in the right direction.
Second: event delegation. Element events "bubble up" the DOM. Instead of attaching a listener to every button we can add one listener to the movie list containing element, and have that catch and process events from its children.
(Note: in this example, instead of logging the details to the console, I'm adding the details to the HTML, and then allowing the button to toggle the element on/off.)
// Cache the movie list element, and attach a listener to it
const movieList = document.querySelector('.movielist');
movieList.addEventListener('click', handleClick);
// Demo data
const data=[{name:"movie1",poster:"img1",details:"Details for movie1."},{name:"movie2",poster:"img2",details:"Details for movie2."},{name:"movie3",poster:"img3",details:"Details for movie3."}];
// `map` over the data to produce your HTML using a
// template string as you've done in your code (no body element)
// Make sure you `join` up the array that `map` returns into a
// whole string once the iteration is complete.
const html = data.map(obj => {
return `
<section class="movie">
<header>${obj.name}</header>
<section class="details">${obj.details}</section>
<button type="button">Movie Details</button>
</section>
`;
}).join('');
// Insert that HTML on to the movie list element
movieList.insertAdjacentHTML('beforeend', html);
// This is the handler for the listener attached to the
// movie list. When that element detects an event from a button
// it finds button's previous element sibling (the section
// with the `.details` class), and, in this case, toggles a show
// class on/off
function handleClick(e) {
if (e.target.matches('button')) {
const details = e.target.previousElementSibling;
details.classList.toggle('show');
}
}
.movie { border: 1px solid #555; padding: 0.5em;}
.movie header { text-transform: uppercase; background-color: #efefef; }
.details { display: none; }
.show { display: block; }
button { margin-top: 0.5em; }
<section class="movielist"></section>
Additional documentation
insertAdjacentHTML
matches
classList
toggle
I know there must be a more efficient way of doing this, I've done it this way in the past because I haven't had many buttons to track, but I now have about 40 buttons that each update updates a mysql table with either a yes or no, and having 40 individual variables and equivalent if statements seems like bad code.
Something to note is that you can see the function has a 1 e.g. onclick='btnChange(1, this.value);. There are 7 different buttons, and then these 7 buttons repeat for onclick='btnChange(2, this.value);. So one solution I thought of is to have 7 if statements for each button and have variable names for each if statement and then I would only have to declare a lot of variables. SO I wasn't sure if that was the best way either. Does this make sense?
HTML
<button type="button" name='foo' value="bar1" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
<button type="button" name='hoo' value="bar2" onclick='btnChange(1, this.value); return false' class='form-control'>Button1</button>
JS
var button1YN = 0;
var button2YN = 0;
and so on...
var YNState;
function btnChange(tableid, btnID) {
if (btnID == "bar1") {
if (button1YN === 0) {
YNState = "yes";
button1YN = 1;
} else {
YNState = "no";
buttonY1N = 0;
}
}
if (btnID == "bar2") {
if (button2YN === 0) {
YNState = "yes";
button2YN = 1;
} else {
YNState = "no";
buttonY2N = 0;
}
}
//ajax code to update the mysql table
}
Instead of having a separate variable for each item, create a single variable to represent the state you're attempting to keep track of. This could be an object or an array, depending on your specific needs and/or preferences.
So you might have a state variable that looks like this for example:
// object mapping table names to on/off state
const state = {
tbl1: true,
tbl2: false,
tbl3: true
}
or an array:
const state = [true, false, true];
If you needed something more complex than true or false (on/off) you could use an array of objects:
const state = [
{
table: 't1',
on: true,
lastModified: '2021-03-23',
someOtherThing: 'foo'
},
{
table: 't2',
on: false,
lastModified: '2021-03-23',
someOtherThing: 'bananas'
},
]
Then you just need a function to update the state when something changes. For the simplest case, the true/false array, it could take the index of the item and the new value:
function updateItem(index, newValue) {
state[index] = newValue;
}
Or just toggle the existing true/false value at the given index:
const toggleItem = index => state[index] = !state[index];
Then just call that from your button click handler.
Here's a quick proof of concept:
// initial state. 7 buttons, all off (no value)
const buttons = Array.from({length: 7});
// function to flip the state at the given index
const toggleButton = index => {
buttons[index] = !buttons[index]; // negate existing value. true becomes false, vice-versa
update(); // redraw the dom
}
// redraws the html.
const update = () => {
const container = document.querySelector('.demo');
// just spitting out a button for each item in the array.
// the key thing here is the click handler, which you might
// want to register differently, but this works for
// demonstration purposes.
container.innerHTML = buttons.map((value, index) => `
<button onclick="toggleButton(${index})">
${value ? "✅" : "🔴"} Button ${index}
</button>
`).join('');
}
// do the initial draw
update();
/* purely cosmetic. irrelevant to functionality */
.demo {
display: flex;
flex-direction: column;
width: 100px;
}
button {
border: none;
padding: 0.5em;
border-radius: 4px;
margin: 0.5em;
}
<div class="demo" />
So I'm trying to build a relatively simple app that would take a group of 8 predetermined celebrities, display two at a time at random, and allow the user to pick which one they prefer until there is only one left. I've managed to get almost everything working correctly on the JavaScript side, but every once in a while I get an error message that can crash the application, and I can't figure out how to fix it. I'd really appreciate any help the community can provide.
I've gone over the code looking for problems in logic that could be causing the error, and I've tried using console.logs to identify it, but the error seems to stop the console.logs themselves from displaying when it occurs.
You can find the GitHub repository here:
https://github.com/jesberman/Celeb-Mash-Prototype
And the live site (with errors) here:
https://jesberman.github.io/Celeb-Mash-Prototype/
An important part of the code is organized as a series of if statements, which I'll display below:
if(initialPicValue1 === initialPicValue2 && initialPicValue2 === celebArrayLength){
initialPicValue2 -= 1;
}
if (initialPicValue1 === initialPicValue2){
initialPicValue2 += 1;
}
if(initialPicValue1 === initialPicValue2 && initialPicValue1 === 0){
initialPicValue2 += 1;
}
if (celebArrayLength === 1){
return alert("Congrats! Your Favorite Celeb is " + celebArray[0].name);
}
I'm looking to get through all of the objects in the array cleanly without any problems, until only one is left. However, I sometimes will randomly get the following error:
Uncaught TypeError: Cannot read property 'picture' of undefined
at pressButton1 (logic.js:128)
at HTMLButtonElement.onclick (index.html:21)
I can see that you are getting an error on the line:
console.log(celebArray[initialPicValue2].picture);
The problem is that you have an array of celebrities, celebArray, and when you click one of the buttons you are accessing an index within that array. The error happens when you get down to only one celebrity left in the array, it tries to access an index that isn't there. So to avoid the error you have to take the first block of code in the pressButton function before if (celebArrayLength === 1) and put it in an if statement of:
if (celebArray.length > 1) {
// do something
}
And then when it runs if (celebArrayLength === 1) and finds there is only one celebrity remaining you could hide the buttons by doing the following because now the game is over.
if (celebArrayLength === 1) {
$('button').hide()
return alert("Congrats! Your Favorite Celeb is " + celebArray[0].name);
}
In your code you have a function for each button but I've simplified it so now there is the one function that gets run for both buttons and you pass in the number of the button like so <button id="button1" onclick="pressButton(1)"> and then you have a function like this function pressButton(e).
So then in that function it has the number of the button in the variable e and you can get the number of the other button by removing e from an array [1,2] and then using the remaining number:
var arr = [1,2] // a list with the numbers of the buttons
arr.splice(e-1, 1) // we remove the one that was pressed
var other = arr[0] // the one left is the other one
I tidied up a few other things like that. Hopefully you can see what I've done. It's generally better to not duplicate functions so that it's easier to just edit one function when you want to change something, and then you don't have to worry about forgetting to do it to both.
So this is my version which fixes your problem. I've linked the images to where they are online, but I've kept the relative links but just commented.
var celebArray = [
{
name: "Tom Hanks",
// picture: "assets/images/tomHanks.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/tomHanks.jpg"
},
{
name: "Benedict Cumberbatch",
// picture: "assets/images/benedictCumberbatch.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/benedictCumberbatch.jpg"
},
{
name: "Charlize Theron",
// picture: "assets/images/charlizeTheron.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/charlizeTheron.jpg"
},
{
name: "Evangeline Lilly",
// picture: "assets/images/evangelineLilly.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/evangelineLilly.jpg"
},
{
name: "Katee Sackhoff",
// picture: "assets/images/kateeSackhoff.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/kateeSackhoff.jpg"
},
{
name: "Robert Downey Jr.",
// picture: "assets/images/robertDowneyJr.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/robertDowneyJr.jpg"
},
{
name: "Rose Leslie",
// picture: "assets/images/roseLeslie.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/roseLeslie.jpg"
},
{
name: "Denzel Washington",
// picture: "assets/images/denzelWashington.jpg"
picture: "https://jesberman.github.io/Celeb-Mash-Prototype/assets/images/denzelWashington.jpg"
},
];
function picValues() {
var initialPicValues = {}
celebArrayLength = celebArray.length;
initialPicValues[1] = Math.floor((Math.random() * celebArrayLength));
var keys = Object.keys(celebArray)
keys.splice(initialPicValues[1], 1)
var rnd_2 = Math.floor((Math.random() * keys.length));
initialPicValues[2] = keys[rnd_2]
return initialPicValues
}
var celebArrayLength = celebArray.length;
var initialPicValues = picValues(celebArray)
function loadPics() {
$("#picture1").css("background-image","url(" + celebArray[initialPicValues[1]].picture + ")");
$("#picture2").css("background-image","url(" + celebArray[initialPicValues[2]].picture + ")");
}
loadPics();
console.log("Initial Array:");
console.log(celebArray);
function pressButton(e) {
if (celebArrayLength > 1) {
var arr = [1,2] // a list with the numbers of the buttons
arr.splice(e-1, 1) // we remove the one that was pressed
var other = arr[0] // the one left is the other one
console.log("Initial Pic "+other+" Value");
console.log(initialPicValues[other]);
console.log("Celeb Being Removed");
console.log(celebArray[initialPicValues[other]].picture);
celebArray.splice(initialPicValues[other], 1);
initialPicValues = picValues(celebArray)
}
if (celebArrayLength === 1) {
$('button').hide()
return alert("Congrats! Your Favorite Celeb is " + celebArray[0].name);
}
console.log("Celeb To Be Removed:")
console.log(celebArray[initialPicValues[other]].picture);
console.log('celebArrayLength', celebArrayLength)
loadPics()
console.log("Array After Button Press:");
console.log(celebArray);
}
#main-div {
padding-top: 50px;
padding-right:10%;
padding-left: 10%;
background-color: gold;
width: 100%;
height: 700px;
}
#header-div {
text-align: center;
background-color: green;
height: 100px;
width: 80%;
}
#left-div {
display: inline-block;
background-color: red;
width: 40%;
height: 400px;
}
#picture1 {
background-repeat: no-repeat;
width: 100%;
height: 300px;
}
#button1 {
position: relative;
left: 40%;
}
#right-div {
display: inline-block;
background-color: blue;
width: 40%;
height: 400px;
}
#picture2 {
background-repeat: no-repeat;
width: 100%;
height: 300px;
}
#button2 {
position: relative;
left: 40%;
}
<!DOCTYPE html>
<head>
<title>
Celeb Mash
</title>
<link rel="stylesheet" href="assets/css/style.css">
<script src="https://code.jquery.com/jquery.js"></script>
</head>
<body>
<div id="main-div">
<div id="header-div">
<h2>
Pick The Celeb You Like More
</h2>
</div>
<div id="left-div">
<div id="picture1">
</div>
<button id="button1" onclick="pressButton(1)">
Submit
</button>
</div>
<div id="right-div">
<div id="picture2">
</div>
<button id="button2" onclick="pressButton(2)">
Submit
</button>
</div>
</div>
<!-- <script src="assets/javascript/logic.js"> -->
</script>
</body>
</html>
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__]]">
I've been trying to implement History.js. I've got some understanding of how getting and pushing states work, however I'm having particular trouble with the data storing component of the history along with using global variables.
As a simple example, I decided to try and set up a script which would change the colour of a html box upon being clicked. This would also trigger the history - essentially creating a history for clicking the box (and its colour being changed on each state of the history).
Is there any way to update a global variable based on the data (in this case, updating i per click) supplied in History State's data?
HTML:
<div id="box">CLICK ME</div>
<button id="back">Back</button>
<button id="forward">Forward</button>
CSS:
#box {
width: 300px;
height: 300px;
background-color: black;
color: white;
vertical-align: middle;
text-align: center;
display: table-cell;
}
JavaScript:
var History = window.History;
var i = 0;
if (History.enabled) {
var State = History.getState();
History.pushState({count:i}, $("title").text(), State.urlPath);
} else {
return false;
}
// Bind to StateChange Event
History.Adapter.bind(window,'statechange', function(){
State = History.getState();
console.log(State.data, State.title, State.url);
$(this).css('background-color', getColour());
});
// Trigger the change
$("#div").on("click", function() {
i++;
History.pushState({count:i},"State " + i,"?state=" + i);
});
function getColour() {
var colours = ["red", "orange", "yellow", "green", "aqua","blue", "purple", "magenta","black"];
if (i > colours.length - 1) {
i = 0;
}
if (i < 0) {
i = colours.length - 1;
}
return colours[i];
}
$("#back").on("click", function() {
History.back();
});
$("#forward").on("click", function() {
History.forward();
});
I'm also using JQuery, ajaxify-html5.js and scrollto.js as per recommendation by other threads.
Editable JSFiddle | Viewable JSFiddle
After playing around with this a ton (and reading more questions), I've figured it out. I'll detail what the solution means to any others who come across this.
JSFIDDLE VIEW SOLUTION | JSFIDDLE VIEW SOLUTION
First here's the final code. Note that the JavaScript has document.ready extras to get it working outside of JSFiddle.
It's also worth noting I took out ajaxify-html5.js and scrollto.js out, as they weren't needed (and were breaking the code somewhere).
HTML:
<div id="box">
<div id="count"></div>
<div id="colour"></div>
</div>
<button id="back">Back</button>
<button id="forward">Forward</button>
CSS:
#box {
width: 300px;
height: 300px;
background-color: white;
color: white;
vertical-align: middle;
text-align: center;
display: table-cell;
}
button {
width: 148px;
height: 40px;
}
#count, #colour {
background-color: black;
font-family: "Consolas";
}
JavaScript:
var History = window.History;
var i = 0;
var colour = getColour();
var colourName = getColourName();
$(document).ready(function() {
if (History.enabled) {
changeHistory();
}
else {
return false;
}
// Bind to StateChange Event
History.Adapter.bind(window,'statechange', function(){
State = History.getState();
i = State.data.count;
colour = State.data.colour;
colourName = State.data.colourName;
changeHistory();
});
// Trigger the change
$(document).on("click", "#box", function() {
i = i + 1;
colour = getColour();
colourName = getColourName();
changeHistory();
});
$(document).on("click", "#back", function() {
History.back();
});
$(document).on("click", "#forward", function() {
History.forward();
});
});
function getColour() {
var colours = ["rgb(220,45,45)", "orange", "rgb(230,230,50)", "rgb(15,210,80)", "rgb(100,220,220)","rgb(50,80,210)", "rgb(140,20,180)", "rgb(230,70,110)","grey"];
if (i > colours.length - 1) {
i = 0;
}
if (i < 0) {
i = colours.length - 1;
}
return colours[i];
}
function getColourName() {
var colourNames = ["Red","Orange","Yellow","Green","Light Blue","Blue","Purle","Pink","Grey"];
return colourNames[i];
}
// Make the changes
function changeHistory () {
$("#colour").html(colourName);
$("#count").html(i);
$("#box").css('background-color', colour);
History.pushState({count:i, colour: colour, colourName: colourName},"A Shade of " + colourName,"?colour=" + colourName);
}
So going back to what I wanted to achieve with the question:
Clicking the box would add history
Each history would hold variables required to affect global variables
Its worth noting the solution specifically uses variables from each iteration of the history to power the global variables, whereas the program itself uses the global variables. The variables used to power the interface never access the ones stored in history.
Let's break up the program into separate and simpler processes and functions. Much like other history.js solutions, there's things you require to get it working:
History.getState(): Gets the latest history item "from the stack"
History.Adapter.bind(window,'statechange', function() {}: An event listener which will trigger a function when the window has a statechange (history change in this case)
History.pushState({data}, title, url): Pushes a state into the "history stack". Holds an object (data), title (of the tab/window) and url to display.
Setting the history:
When a user clicks on the box, the program should:
increment the counter (i)
change the colour and colourName
add the new history stack object in
I decided to separate the first two features from the last one. The function changeHistory() is responsible for updating the contents of the box (from global variables) and pushing a new history object in (using global variables).
changeHistory() gets called whenever I want to add a new item of history in and update the contents in the box to reflect the new history - so at launch at when the box is clicked.
When the box is clicked, the first two criteria get met. Using the existing global variables and functions, the new colour and name are retrieved and set as the global variables.
This is how it should behave:
Box Click -> Increment i, Change variables -> Push History
Listen for the history change:
Once a history change has been made (either by clicking the box, pressing back/forward buttons or browser buttons), a change needs to occur.
By creating the variable State = History.getState(), we have an easy way of accessing the data from the latest history stack object.
We'll use this data from the history object.data to assign to the global variables. After updating the variables, we'll update the view using changeHistory().
This is how the model should work:
History changed -> Update globals from history -> Update View
History change will occur whenever someone presses back, forwards or the box, accounting for all possible changes.