How to style specific letters in string? - javascript

Let's assume that I receive a response from the API that contains a summary of the last matches of a given team.
Example response: LLWWWWLWDWDWLW
I want it to be in my div in specific style.
L should be stylized, its background should be red (square shape) and W should have a green background. The entire statement should be minimally separated from each other.
User has the option to change the team to check its recent matches.
I'm using Vue.js

One quick solution with v-for:
const mapColors = new Map([['W', 'green'], ['L', 'red'], ['D', 'yellow']])
const app = Vue.createApp({
data() {
return {
matches: [{team: 'team 1', games: 'LLWWWWLWDWDWLW'}, {team: 'team 2', games: 'WWWWLWDLLWWWDW'}]
};
},
methods: {
setColor(c) {
return mapColors.get(c)
}
}
})
app.mount('#demo')
.green {
background: limegreen;
}
.red {
background: lightcoral;
}
.yellow {
background: gold;
}
.games {
width: 1.2em;
display: inline-block;
text-align: center;
margin: .1em;
}
.match {
margin-right: .5em;
font-weight: 600;
}
<script src="https://unpkg.com/vue#3/dist/vue.global.prod.js"></script>
<div id="demo">
<div v-for="(match, idx) in matches" :key="idx">
<span class="match">{{ match.team }}</span>
<span v-for="(game, i) in match.games" :key="i">
<span class="games" :class="setColor(game)">
{{ game }}
</span>
</span>
</div>
</div>

Related

How check old data in vue component after rendering?

I'm trying to create a simple marketcap checker for crytpo (like coinmarketcap) using coingecko api.
I can fetch the data and render it, no problem with that. And I fetch the data 2 times per minutes.
But now, I would like to check if the new price is higher or lower than the last price.
I do a v-for loop and I pass some data in my "tokenComponent" for rendering the data like this :
<template>
<div id="app">
<div class="container mx-auto">
<div class="pt-6">
<h1 class="text-2xl font-bold">Crypto Monkey Cap</h1>
<div v-for="token in listTokens" :key="token.id">
<div class="py-6">
<token-component
:name="token.name"
:price="token.current_price"
:mcap="token.market_cap"
></token-component>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import TokenComponent from "./components/tokenComponent.vue";
export default {
name: "App",
components: {
TokenComponent,
},
data() {
return {
listTokens: [],
lastPrice: 0
};
},
mounted() {
this.getTokens();
setInterval(() => {
this.getTokens()
}, 30000);
},
methods: {
getTokens() {
fetch("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd")
.then((response) => response.json())
.then((data) => {
this.listTokens = data;
});
}
}
};
</script>
and the tokenComponent :
<template>
<div class="py-4 border-2 rounded-lg">
<div class="flex justify-around">
<h2>{{ name }}</h2>
<h2>{{ price }} $</h2>
<h2>{{ mcap }} $</h2>
</div>
</div>
</template>
<script>
export default {
props: {
name: { required: true },
price: { required: true },
mcap: { required: true }
}
};
</script>
I just would like put a conditionnal class in price data if the last price is higher or lower than the new one...
(I'm new in Vuejs... ;) )
You should store previous prices to calculate if the last price is higher or lower than the new one. Use Array for that.
Added small example using setInterval instead of fetching new prices to display indicators
new Vue({
el: "#app",
data: () => ({
prices: [1]
}),
methods: {
stonks(index) {
if (index > 0) {
return (this.prices[index] - this.prices[index-1]) > 0
? 'green' : 'red'
}
}
},
mounted() {
setInterval(() => {
this.prices.push(Math.floor(Math.random() * 10) + 1)
}, 2000)
}
})
.prices {
display: flex;
flex-direction: row;
padding: 10px;
}
.price {
border:1px solid #bbb;
border-radius: 5px;
padding: 10px;
width: 32px;
height: 32px;
line-height: 32px;
text-align: center;
margin-right: 5px;
position: relative;
}
.stonks {
position: absolute;
background: grey;
border-radius: 50%;
width: 16px;
height: 16px;
top: 0;
right: 0;
margin-top:-8px;
margin-right:-8px
}
.stonks.red { background: red; }
.stonks.green { background: green; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div class="prices">
<div
v-for="(price, index) in prices"
:key="index"
class="price"
>
{{ price }}
<div class="stonks" :class="stonks(index)" />
</div>
</div>
</div>

React Adding Image in accordion

I am trying to create a FAQ page and I am having below three files:
App.js
import React, { useState } from 'react';
import Header from './Header';
import FAQ from './FAQ';
function App () {
const [faqs, setfaqs] = useState([
{
id: 1,
question: 'This is question 1?',
answer: 'This is answer 1',
open: false
},
{
id: 2,
question: 'This is question 2?',
answer: 'This is answer 2',
open: false
},
{
id: 3,
question: 'This is question 3?',
answer: 'This is answer 3',
image: 'Question3.png',
open: false
},
{
id: 4,
question: 'This is question 4?',
answer: 'This is answer 4',
open: false
}
]);
const toggleFAQ = index => {
setfaqs(faqs.map((faq, i) => {
if (i === index) {
faq.open = !faq.open
} else {
faq.open = false;
}
return faq;
}))
}
return (
<div className="App">
<Header />
<div className="faqs">
{faqs.map((faq, i) => (
<FAQ faq={faq} index={i} key={faq.id} toggleFAQ={toggleFAQ} />
))}
</div>
</div>
);
}
export default App;
FAQ.js
import React from 'react'
function FAQ ({faq, index, toggleFAQ}) {
// console.log(faq.hasOwnProperty('url'));
if(faq.hasOwnProperty('url') && faq.hasOwnProperty('image')){
console.log("Hello URL");
return (
<div
className={"faq " + (faq.open ? 'open' : '')}
key={faq.id}
onClick={() => toggleFAQ(index)}
>
<div>
<div className="faq-question">
{faq.question}
</div>
<div className="faq-answer">
{faq.answer}
<div className="faq-url">
<a href={faq.url}>Link to URL</a>
</div>
<div className="faq-image">
<img src={`/src/media/${faq.image}`} alt="Image for FAQ"/>
</div>
</div>
</div>
</div>
)
}
else {
console.log("No url");
return (
<div
className={"faq " + (faq.open ? 'open' : '')}
key={faq.id}
onClick={() => toggleFAQ(index)}
>
<div>
<div className="faq-question">
{faq.question}
</div>
<div className="faq-answer">
{faq.answer}
</div>
</div>
</div>
)
}
}
export default FAQ;
Index.css
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
"Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
background-color: rgb(255, 255, 255);
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}
header {
display: flex;
justify-content: center;
align-content: center;
padding: 5px;
background-color:#4caf50;
border-bottom: 3px solid #3c3c3c;
}
header h1 {
color: rgb(26, 25, 25);
font-size: 28px;
font-weight: 700;
text-transform: uppercase;
}
.faqs {
width: 100%;
max-width: 768px;
margin: 0 auto;
padding: 15px;
}
.faqs .faq {
margin: 20px;
padding: 15px;
background-color:#f5f5f5 ;
border: 2px solid black;
border-radius: 8px;
box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.2);
}
.faqs .faq:hover {
background-color: #f0ebeb;
}
.faqs .faq .faq-question {
position: relative;
font-size: 20px;
padding-right: 80px;
transition: all 0.4s ease;
}
.faqs .faq .faq-question::after {
content: '\2B9B';
position: absolute;
color: black;
top: 50%;
right: 0px;
transform: translateY(-50%);
margin-top: 10px;
width: 50px;
height: 50px;
transition: all 0.4s ease-out;
}
.faqs .faq .faq-answer {
opacity: 0;
max-height: 0;
overflow-y: hidden;
transition: all 0.4s ease-out;
}
.faqs .faq.open .faq-question {
margin-bottom: 15px;
}
.faqs .faq.open .faq-question::after {
/* transform: translateY(-70%) rotate(180deg); */
content: "\2B99";
}
.faqs .faq.open .faq-answer {
max-height: 1000px;
opacity: 1;
}
Directory structure of all the pages are like below:
App.js consist of state of objects containing various question and answers for FAQ page. Also it consist of FAQToggle which checks if Accordion is open or not and closes the accordion if it is open and vice versa.
FAQ.js Line:24(Actual issue which I am facing): I am trying to add an image so that answer 3 can hold the image. But all I can see is a broken image icon. Can anyone help me in debugging this?
I think you can only use photos in your components if you import then first, let me know if im wrong. https://create-react-app.dev/docs/adding-images-fonts-and-files/
This post says you "need" to import them first: https://www.edwardbeazer.com/importing-images-with-react/
Heres another: https://daveceddia.com/react-image-tag/
Quote - "A common mistake for beginners is to set the src to a file path on their computer, like /Users/yourname/Projects/this-react-app/src/image.png. That won’t work."
Quote - "Browsers are mostly sandboxed these days and won’t let you access files by their path on disk. If you did get that to work (maybe with file://), it’d break as soon as you deployed the app, because a web server won’t have that file in the same place! (And no, the solution is not to put it in the same place on the server"
import React, { useState } from 'react';
import Header from './Header';
import FAQ from './FAQ';
import Question3 from "./media/Question3.png" //import img here
function App () {
const [faqs, setfaqs] = useState([
{
id: 1,
question: 'This is question 1?',
answer: 'This is answer 1',
open: false
},
{
id: 2,
question: 'This is question 2?',
answer: 'This is answer 2',
open: false
},
{
id: 3,
question: 'This is question 3?',
answer: 'This is answer 3',
image: Question3, // use it as a variable
open: false
},
{
id: 4,
question: 'This is question 4?',
answer: 'This is answer 4',
open: false
}
]);
const toggleFAQ = index => {
setfaqs(faqs.map((faq, i) => {
if (i === index) {
faq.open = !faq.open
} else {
faq.open = false;
}
return faq;
}))
}
return (
<div className="App">
<Header />
<div className="faqs">
{faqs.map((faq, i) => (
<FAQ faq={faq} index={i} key={faq.id} toggleFAQ={toggleFAQ} />
))}
</div>
</div>
);
}
export default App;
if(faq.hasOwnProperty('url') && faq.hasOwnProperty('image')){
console.log("Hello URL");
return (
<div
className={"faq " + (faq.open ? 'open' : '')}
key={faq.id}
onClick={() => toggleFAQ(index)}
>
<div>
<div className="faq-question">
{faq.question}
</div>
<div className="faq-answer">
{faq.answer}
<div className="faq-url">
<a href={faq.url}>Link to URL</a>
</div>
<div className="faq-image">
<img src={faq.image} alt="Image for FAQ"/> //target it here
</div>
</div>
</div>
</div>

How to call method on click as opposed to on v-for in Vuejs

I'm trying to display this array of objects based on highest to lowest rating of each item. It works fine using the method on the v-for, but I would really like to display the list of objects in the order they appear initially and THEN call a function that displays them by highest to lowest rating.
When I have it set as v-for="item in items" and then try to call the method on a button, such as #click="rated(items)", nothing happens. Why would I be able to display the array initially on the v-for with the method attached, but not on a click event?
const items = [
{
name: "Bert",
rating: 2.25
},
{
name: "Ernie",
rating: 4.6
},
{
name: "Elmo",
rating: 8.75
},
{
name: "Rosita",
rating: 2.75
},
{
name: "Abby",
rating: 9.5
},
{
name: "Cookie Monster",
rating: 5.75
},
{
name: "Oscar",
rating: 6.75
}
]
new Vue({
el: "#app",
data: {
items: items
},
methods: {
rated: function(items) {
return items.slice().sort(function(a, b) {
return b.rating - a.rating;
});
},
sortByRating: function(items) {
return items.slice().sort(function(a, b) {
return b.rating - a.rating;
});
}
}
});
#app {
display: flex;
flex-flow: row wrap;
margin-top: 3rem;
}
.item {
flex: 1;
margin: .5rem;
background: #eee;
box-shadow: 0px 2px 4px rgba(0,0,0,.5);
padding: 1rem;
min-width: 20vw;
}
.toggle {
position: absolute;
top: 10px;
left: 10px;
padding: .5rem 1rem;
background: DarkOrchid;
color: #fff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<div v-for="item in rated(items)"
class="item">
<p>{{item.name}}</p>
<p>{{item.rating}}</p>
</div>
</div>
Try to rewrite the array for the result returned by the method, like this.
#click="items = rated(items)
and, inside v-for you can keep using items.

How to add class from content of a variable in Vue.js

I want to pass a variable to a component which should be added as a class to a div. However, Vue adds the name of the variable instead of the content.
So I pass the prop color which contains red.
What I want: <div class="red">2</div>
What I get: <div class="color">2</div>
I think this is a noob question, so sorry for that. Maybe there is a better way to do this.
Thanks for helping me out.
Here are my components:
<template>
<div class="btn btn-outline-primary cell"
:class="{color}"
:id="id">
{{ number }}
</div>
</template>
<script>
export default {
name: "TileElement",
props: ["id", "number", "color"]
}
</script>
<style scoped>
.green {
color: green;
}
.red {
color: red;
}
.yellow {
color: yellow;
}
.cell {
display: inline-block;
float: left;
margin: 0.1em;
text-align: center;
background-color: lightgray;
width: 2.7em;
height: 2.7em;
}
</style>
Parent Component:
<template>
<div class="row">
<TileElement
v-for="tile in tiles"
v-bind:key="tile.id"
v-bind:number="tile.number"
v-bind:color="tile.color"
></TileElement>
</div>
</template>
<script>
import TileElement from './TileElement.vue';
export default {
name: "TileRow",
components: {
TileElement
},
data: function () {
return {
tiles: [
{id: 1, number: 1, color: "green"},
{id: 2, number: 2, color: "red"},
{id: 3, number: 3, color: "yellos"}
]
}
}
}
</script>
If you just need to pass a class, without any conditions or other such stuff, then you can simple use array for one and any other number of classes:
<div :class="[color]"></div>
But that's not only you can do.
https://v2.vuejs.org/v2/guide/class-and-style.html
:class="color" will also work:
var vm = new Vue({
el: '#app',
data() {
return {
color: 'red'
};
}
});
.cell { width: 50px; height: 50px; }
.red { background: red; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.21/vue.min.js"></script>
<div id="app">
<div class="cell" :class="color"></div>
</div>
Try dynamically importing the class name
:class="{[color]: true}"

Reactive object in 2 arrays

I have an array of objects (array 1), that can be toggled to another array (array 2). When added the user has the option to type in a text field for each option. The toggling works fine and is reactive on the initial creation. But if I have data that already exists in array 2, the item is no longer reactive.
I have made a quick jsfiddle to demonstrate: Event 1 and 3 are reactive, but event 2 no longer is as it already exists in the newEvents array. Is there anyway to get this connected to the original event?
new Vue({
el: "#app",
data: {
events: [
{ id: 1, text: "Event 1"},
{ id: 2, text: "Event 2"},
{ id: 3, text: "Event 3"}
],
savedEvents: [
{ id: 2, text: "Event 2", notes: 'Event Notes'}
]
},
methods: {
toggleEvent: function(event){
let index = this.savedEvents.findIndex(e => e.id == event.id);
if (index != -1) {
this.savedEvents.splice(index, 1);
} else {
this.savedEvents.push(event);
}
},
inArray: function(id) {
return this.savedEvents.some(obj => obj.id == id);
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.btn {
display: inline-block;
padding: 5px;
border: 1px solid #666;
border-radius: 3px;
margin-bottom: 5px;
cursor: pointer;
}
input[type=text]{
padding: 5px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Events:</h2>
<ol>
<li v-for="event in events">
<span class="btn" #click="toggleEvent(event)">
{{ event.text }}
</span>
<input type="text" placeholder="Type your note here..." v-model="event.notes" v-if="inArray(event.id)">
</li>
</ol>
<h2>
Saved Events:
</h2>
<ul>
<li v-for="event in savedEvents">
<strong>{{ event.text }}</strong> {{ event.notes }}
</li>
</ul>
</div>
The problem here is nothing to do with reactivity.
When you add an event to newEvents by clicking the button it's using the same object that's in events. As there's only one object for each event everything work fine.
In the case of Event 2 you're starting with two separate objects representing the same event, one in events and the other in newEvents. Changes to one will not change the other.
It's difficult to say what the appropriate solution is here without knowing your motivation for choosing these data structures but the example below ensures that both arrays contain the same object for Event 2.
The only thing I've changed from your original code is the data function.
new Vue({
el: "#app",
data () {
const data = {
events: [
{ id: 1, text: "Event 1"},
{ id: 2, text: "Event 2", notes: 'Event Notes'},
{ id: 3, text: "Event 3"}
],
newEvents: []
}
data.newEvents.push(data.events[1])
return data
},
methods: {
toggleEvent: function(event){
let index = this.newEvents.findIndex(e => e.id == event.id);
if (index != -1) {
this.newEvents.splice(index, 1);
} else {
this.newEvents.push(event);
}
},
inArray: function(id) {
return this.newEvents.some(obj => obj.id == id);
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.btn {
display: inline-block;
padding: 5px;
border: 1px solid #666;
border-radius: 3px;
margin-bottom: 5px;
cursor: pointer;
}
input[type=text]{
padding: 5px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Events:</h2>
<ol>
<li v-for="event in events">
<span class="btn" #click="toggleEvent(event)">
{{ event.text }}
</span>
<input type="text" placeholder="Type your note here..." v-model="event.notes" v-if="inArray(event.id)">
</li>
</ol>
<h2>
New Events:
</h2>
<ul>
<li v-for="event in newEvents">
<strong>{{ event.text }}</strong> {{ event.notes }}
</li>
</ul>
</div>
There are various ways you could represent this data other than by using two lists of the same objects. You might use a boolean flag within the objects. Or you could use a separate object to hold the notes, keyed by event id. It's difficult to know what would work best for your scenario.
Update:
Based on the comments, you could do something like this to use the objects in events as canonical versions when loading savedEvents:
loadSavedEvents () {
// Grab events from the server
someServerCall().then(savedEvents => {
// Build a map so that the objects can be grabbed by id
const eventMap = {}
for (const event of this.events) {
eventMap[event.id] = event
}
// Build the list of server events using the objects in events
this.savedEvents = savedEvents.map(savedEvent => {
const event = eventMap[savedEvent.id]
this.$set(event, 'notes', savedEvent.notes)
return event
})
})
}
As pointed out by #skirtle the object from the list array needs to be pushed into the second array for it to be reactive. I have solved this by looping through and matching the id and then pushing this object into the second array. Not sure if this is the best / most efficient way to do this but it works now.
new Vue({
el: "#app",
data: {
eventsList: [
{ id: 1, text: "Event 1"},
{ id: 2, text: "Event 2"},
{ id: 3, text: "Event 3"}
],
savedEvents: [
{ id: 2, text: "Event 2", notes: 'Event Notes'}
]
},
mounted() {
this.init();
},
methods: {
init: function() {
let _temp = this.savedEvents;
this.savedEvents = [];
_temp.forEach(event => {
this.eventsList.forEach(x => {
if (event.id == x.id) {
this.$set(x, "notes", event.notes);
this.savedEvents.push(x);
}
});
});
},
toggleEvent: function(event){
let index = this.savedEvents.findIndex(e => e.id == event.id);
if (index != -1) {
this.savedEvents.splice(index, 1);
} else {
this.savedEvents.push(event);
}
},
inArray: function(id) {
return this.savedEvents.some(obj => obj.id == id);
}
}
})
body {
background: #20262E;
padding: 20px;
font-family: Helvetica;
}
#app {
background: #fff;
border-radius: 4px;
padding: 20px;
transition: all 0.2s;
}
li {
margin: 8px 0;
}
h2 {
font-weight: bold;
margin-bottom: 15px;
}
.btn {
display: inline-block;
padding: 5px;
border: 1px solid #666;
border-radius: 3px;
margin-bottom: 5px;
cursor: pointer;
}
input[type=text] {
padding: 5px;
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h2>Events:</h2>
<ul>
<li v-for="event in eventsList">
<span class="btn" #click="toggleEvent(event)">
{{ event.text }}
</span>
<input type="text" placeholder="Type your note here..." v-model="event.notes" v-if="inArray(event.id)">
</li>
</ul>
<h2>
Saved Events:
</h2>
<ul>
<li v-for="event in savedEvents">
<strong>{{ event.text }}</strong> {{ event.notes }}
</li>
</ul>
</div>
Try using $set and $delete for avoiding reactivity lost
https://v2.vuejs.org/v2/api/?#Vue-set
https://v2.vuejs.org/v2/guide/reactivity.html

Categories

Resources