I am trying to add a font-awesome arrow icon via my css code like this:
<style>
.negative {
color: red;
}
.positive {
color: green;
}
.negative::after {
content: "\f106";
}
</style>
I have font-awesome included in my html via CDN. For some reason my icon does not display properly, it just shows a square. Any ideas why and how I can fix it?
Here is the rest of my code, showing the logic behind the displaying of percentages:
<template>
<div>
<v-data-table
:headers="headers"
:items="rowsToDisplay"
:hide-default-footer="true"
class="primary"
>
<template #item.thirtyDaysDiff="{ item }">
<span :class="item.thirtyDaysDiffClass">{{ item.thirtyDaysDiff }}%</span>
</template>
<template #item.sevenDaysDifference="{ item }">
<span :class="item.sevenDaysDiffClass">{{ item.sevenDaysDiff }}%</span>
</template>
</v-data-table>
</div>
</template>
<script>
import axios from 'axios';
export default {
data () {
return {
bitcoinInfo: [],
isPositive: false,
isNegative: false,
headers: [
{
text: 'Currency',
align: 'start',
value: 'currency',
},
{ text: '30 Days Ago', value: '30d' },
{ text: '30 Day Diff %', value: 'thirtyDaysDiff'},
{ text: '7 Days Ago', value: '7d' },
{ text: '7 Day Diff %', value: 'sevenDaysDifference' },
{ text: '24 Hours Ago', value: '24h' },
],
}
},
methods: {
getBitcoinData() {
axios
.get('data.json')
.then((response => {
var convertedCollection = Object.keys(response.data).map(key => {
return {currency: key, thirtyDaysDiff: 0, sevenDaysDifference: 0, ...response.data[key]}
})
this.bitcoinInfo = convertedCollection
}))
.catch(err => console.log(err))
},
calculateDifference(a, b) {
let calculatedPercent = 100 * Math.abs((a - b) / ((a + b) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
},
getDiffClass(a, b) {
return a > b ? 'positive': a < b ? 'negative' : ''
},
calculateSevenDayDifference(item) {
let calculatedPercent = 100 * Math.abs((item['24h'] - item['7d']) / ((item['24h'] + item['7d']) / 2));
return Math.max(Math.round(calculatedPercent * 10) / 10, 2.8).toFixed(2);
}
},
computed: {
rowsToDisplay() {
return Object.keys(this.bitcoinInfo)
.map(key => {
return {
currency: key,
...this.bitcoinInfo[key]
}
}).map((item) => ({
...item,
thirtyDaysDiff: this.calculateDifference(item['7d'], item['30d']),
thirtyDaysDiffClass: this.getDiffClass(item['7d'], item['30d']),
sevenDaysDiff: this.calculateDifference(item['24h'], item['7d']),
sevenDaysDiffClass: this.getDiffClass(item['24h'], item['7d']),
}))
}
},
mounted() {
this.getBitcoinData()
}
}
</script>
have you tried to import your icons inside the template area with <i ...></i>?
here is an working example. Check out the cdn.fontawesome/help-page to get more information.
Vue.createApp({
data () {
return {
isPositive: false,
isNegative: true
}
}
}).mount('#demo')
.negative {
color: red;
}
.positive {
color: green;
}
.neutral {
color: #666;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.3/css/all.min.css" rel="stylesheet"/>
<script src="https://unpkg.com/vue#next"></script>
<div id="demo">
<i class="fas" :class="isPositive ? 'fa-angle-up positive' : isNegative ? 'fa-angle-down negative' : 'fa-minus neutral' "></i>
<br>
<br>
<button #click="isPositive = !isPositive; isNegative = !isNegative" v-text="'change pos and neg'" />
</div>
so basically you'll bind the icon classes to your own conditions. You could write the conditions for example with the tenary operator into your template area. Hope you get the idea.
Host Fontawesome yourself by following the steps in this Fontawesome documentation.
https://fontawesome.com/docs/web/setup/host-yourself/webfonts
i hope this help.
Related
Hi all, i have a problem in vuedraggable. When i drag "item 2" to "item 3" => I want "item 3" will swap with "item 2".
Please help me.
Assuming you have multiple lists and each list has its own <draggable> element, then you will need to assign a new method handleMove(event) to your <draggable> elements. The event parameter is important because event.draggedContext contains the index of the item that you are trying to move (index) and also the index of the other item which is currently overlapping with your grabbed item (futureIndex). It also contains information from which list is your grabbed item (event.from) and where you want to drop it (event.to). Store these 4 variables somewhere and use them when you are done dragging (function handleDragEnd()).
Inside handleDragEnd() simply swap those 2 items and Vue will update the HTML template.
LIVE DEMO HERE
<template>
<div class="row">
<div class="col-3">
<h3>My LEGO Bionicles</h3>
<draggable
class="list-group"
data-list="list1"
:list="list1"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
<div class="col-3">
<h3>Favourite LEGO Bionicle</h3>
<draggable
class="list-group"
data-list="list2"
:list="list2"
group="bionicles"
#change="log"
itemKey="id"
:move="handleMoveItem"
#end="handleDragEndItem"
:options="{ animation: 500 }"
>
<template #item="{ element, index }">
<div class="list-group-item" :style="element.style">
{{ element.name }}
</div>
</template>
</draggable>
</div>
</div>
</template>
<script>
import draggable from 'vuedraggable';
export default {
name: 'two-lists-swap',
display: 'Swapping between 2 lists',
order: 1,
components: {
draggable,
},
data() {
return {
list1: [
{ name: 'TOA Mata Nui', id: 1, style: { background: 'gold' } },
{
name: 'TOA Tahu',
id: 2,
style: { background: 'red', color: 'yellow' },
},
{ name: 'TOA Kopaka', id: 3, style: { background: 'white' } },
{
name: 'TOA Anakin',
id: 4,
style: { background: 'black', color: 'yellow' },
},
],
list2: [
{
name: 'TOA Gali',
id: 5,
style: { background: 'blue', color: 'yellow' },
},
{
name: 'TOA Lewa',
id: 6,
style: { background: 'green', color: 'yellow' },
},
{
name: 'TOA Pohatu',
id: 7,
style: { background: 'brown', color: 'white' },
},
],
};
},
methods: {
handleDragEndItem() {
if (this.originalList === this.futureList) {
this.movingItem = this[this.futureList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _list = Object.assign([], this[this.futureList]);
_list[this.futureIndex] = this.movingItem;
_list[this.originalIndex] = this.futureItem;
this[this.futureList] = _list;
}
} else {
this.movingItem = this[this.originalList][this.originalIndex];
this.futureItem = this[this.futureList][this.futureIndex];
if (this.movingItem && this.futureItem) {
let _listFrom = Object.assign([], this[this.originalList]);
let _listTo = Object.assign([], this[this.futureList]);
_listTo[this.futureIndex] = this.movingItem;
_listFrom[this.originalIndex] = this.futureItem;
this[this.originalList] = _listFrom;
this[this.futureList] = _listTo;
}
}
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
this.$toast.show('dragEnd');
},
handleMoveItem(event) {
document
.querySelectorAll('.list-group-item')
.forEach((el) => (el.style.border = 'none'));
const { index, futureIndex } = event.draggedContext;
this.originalIndex = index;
this.futureIndex = futureIndex;
this.originalList = event.from.getAttribute('data-list');
this.futureList = event.to.getAttribute('data-list');
if (this[this.futureList][this.futureIndex]) {
event.to.children[this.futureIndex].style.border = '2px solid orange';
}
return false; // disable sort
},
},
};
</script>
<style>
.list-group-item {
padding: 5px 10px;
cursor: grab;
}
</style>
On my Next js project I am looping through an array of amenities that displays a div that when clicked toggles an active prop. The addAmenity function handles the logic that loops through the amenities array and toggles the specific array item's active property. If the active prop of the div is true the .amenities-active class is supposed to be applied to it and the background of the div should turn green but it does not. Is there any idea as to what I am doing wrong. The console.log(tempList) confirms a change to true when clicked on a false amenity but the UI does not change to have a green background color.
//Next js
const amenitiesDefaultArr = [
{
data: "bathroom",
text: "Private Bathroom",
icon: <WcIcon fontSize="large" />,
active: false,
},
{
data: "dining",
text: "Dining Hall",
icon: <FastfoodIcon fontSize="large" />,
active: false,
},
{
data: "wifi",
text: "Wifi",
icon: <WifiIcon fontSize="large" />,
active: false,
}
]
const addAmenity = (e) => {
let dataItem = e.currentTarget.dataset.amenity
let tempList = amenitiesList
tempList.map(el => {
if (el.data === dataItem) el.active = !el.active
return el
})
console.log(tempList)
setAmenitiesList(tempList)
}
const AddDorm = () => {
const [amenitiesList, setAmenitiesList] = useState(amenitiesDefaultArr)
return (
<>
{
amenitiesList.map(el => {
const {data, text, icon } = el
let { active } = el
return (
<div
className={`amenity ${active && `amenity-active`}`}
key={data}
data-amenity={data}
onClick={(e) => addAmenity(e)}
>
<p>{text}</p>
{icon}
</div>
)
</>
})
)
/* CSS */
.amenity {
padding: 0.5rem 1rem;
display: flex;
align-items: center;
border-radius: 50px;
box-shadow: 5px 5px 10px #919191,
-5px -5px 10px #ffffff;
z-index: 4;
cursor: pointer;
}
.amenity-active {
background-color: var(--green);
}
The main problem is you are trying to pass data in a JSX element as you would do in html.
data-amenity={data}
React does work this way.So, e.currentTarget.dataset.amenity is always undeined.Instead, React uses refs to access dom elements. You can learn more about refs in the official React documentation. But in your case, you don't even need any ref as you can send data directly to any function. Check:
<div
className={`amenity ${active && `amenity-active`}`}
key={data}
// data-amenity={data}
onClick={() => addAmenity(data)}
>
<p>{text}</p>
</div>
and in addAmenity just receive it
const addAmenity = (incoming) => {
let dataItem = incoming
...
}
Below I provide the version of your code I fixed for you which is working perfect. Please let me know if this was helpful.
//Next js
import { useState } from 'react'
const amenitiesDefaultArr = [
{
data: "bathroom",
text: "Private Bathroom",
icon: <WcIcon fontSize="large" />,
active: false,
},
{
data: "dining",
text: "Dining Hall",
icon: <FastfoodIcon fontSize="large" />,
active: false,
},
{
data: "wifi",
text: "Wifi",
icon: <WifiIcon fontSize="large" />,
active: false,
}
]
const AddDorm = () => {
const [amenitiesList, setAmenitiesList] = useState(amenitiesDefaultArr)
const addAmenity = (incoming) => {
let dataItem = incoming
const tempList = amenitiesList.map(el => {
if (el.data === dataItem) el.active = !el.active
return el
})
console.log(tempList)
setAmenitiesList(tempList)
}
return (
<>
{
amenitiesList.map(el => {
const {data, text, icon} = el
let { active } = el
return (
<div
className={`amenity ${active && `amenity-active`}`}
key={data}
// data-amenity={data}
onClick={() => addAmenity(data)}
>
<p>{text}</p>
{icon}
</div>
)})}
</>
)
}
export default AddDorm
I have a page on my website in which I display some cards in a masonry layout.
It's implemented in Vue 3 and I want the layout do adapt to the container's child heights.
The layout flows from top to bottom, left to right. Just like the images shows:
To achieve this, I divided the cards in different columns using the order CSS property together with a flexbox.
However, for this to work, the parent container needs to have a fixed height.
I want this to be the minimum height possible to make sure all cards fit, that is, the exact height of the longest column of the layout.
I tried to set the container's height to 0 initially and then update it based on the card's height, however, this doesn't work very well and is really janky.
<template>
<section class="container" :style="{ height: containerHeight }">
<project-card v-for="i in projects.length" :key="i" :project="projects[i - 1]"
:style="{ order: (i - 1) % numberColumns + 1, width: (100 / numberColumns) - 1.5 + '%' }"
:ref="setProjectCardRef">
</project-card>
<span v-for="i in numberColumns - 1" :key="i" :style="{ order: i }" class="item break"></span>
</section>
</template>
<script>
import Projects from "#/api/Projects";
import ProjectCard from "#/components/ProjectCard";
export default {
name: "Projects",
components: {
"project-card": ProjectCard,
},
data() {
return {
projects: [],
projectCardsRefs: [],
windowWidth: window.innerWidth,
containerHeight: "100%"
}
},
mounted() {
this.getData();
window.addEventListener('resize', () => {
this.windowWidth = window.innerWidth
})
},
methods: {
getData() {
Projects.list().then((response) => {
for (let project of response.data)
project.image_url = process.env.VUE_APP_API_ENDPOINT + project.image_url;
this.projects = response.data;
});
},
setProjectCardRef(el) {
if (!this.projectCardsRefs.includes(el))
this.projectCardsRefs.push(el)
}
},
computed: {
numberColumns() {
return Math.round(this.windowWidth / 400)
},
},
async updated() {
await new Promise(r => setTimeout(r, 200));
let heights = Array(this.numberColumns).fill(0)
for (let i = 0; i < this.projectCardsRefs.length; i++) {
const style = this.projectCardsRefs[i].$el.currentStyle || window.getComputedStyle(this.projectCardsRefs[i].$el);
const marginTop = parseInt(style.marginTop.match(/\d+/g)[0]);
const height = parseFloat(style.height.match(/\d+(.\d+)?/g)[0]);
heights[i % this.numberColumns] += height + marginTop
}
this.containerHeight = 40 + Math.max(...heights) + "px";
}
}
</script>
<style scoped>
.container {
#apply flex flex-col flex-wrap space-y-6;
}
.break {
#apply mx-3 w-0;
flex-basis: 100%;
}
</style>
How can I set the container's height based on its children in a more responsive way?
Following Paulie_D's suggestion, I implemented it using Masonry.JS
<template>
<section id="container" v-masonry transition-duration="0.2s" item-selector=".item"
percent-position="true" ref="container" :gutter="spaceBetween">
<project-card class="item" v-masonry-tile v-for="i in projects.length" :key="i" :project="projects[i - 1]"
:style="`width: ${itemWidth}px; margin-bottom: ${spaceBetween}px`"/>
</section>
</template>
<script>
import Projects from "#/api/Projects";
import ProjectCard from "#/components/ProjectCard";
export default {
name: "Projects",
components: {
"project-card": ProjectCard,
},
data() {
return {
projects: [],
containerWidth: 0,
spaceBetween: 20
}
},
mounted() {
this.getData();
new ResizeObserver(this.onResize).observe(document.getElementById("container"))
this.onResize()
},
methods: {
getData() {
Projects.list().then((response) => {
for (let project of response.data)
project.image_url = process.env.VUE_APP_API_ENDPOINT + project.image_url;
this.projects = response.data;
});
},
onResize() {
this.containerWidth = document.querySelector("main").offsetWidth;
}
},
computed: {
numberColumns() {
return Math.round(this.containerWidth / 400);
},
itemWidth() {
return (this.containerWidth - this.numberColumns * this.spaceBetween) / this.numberColumns;
}
}
}
</script>
The component has the following binding:
<div
class="columns dropdowns"
:style="{
height: `${dropdownsgridheight}px`,
}"
>
That height is getting calculated based on count of dropdowns. On mobile screens, it shows poorly. So I need to remove this style binding when screen height is less than 812px. How can it be done correctly in Vue.js?
Try this:
<div
class="columns dropdowns"
:style="finalHeight"
></div>
and in your component:
data() {
return {
finalHeight: ''
}
},
created() {
window.addEventListener("resize", this.myEventHandler);
},
destroyed() {
window.removeEventListener("resize", this.myEventHandler);
},
methods: {
myEventHandler() {
if(window.innerHeight > 812) {
this.finalHeight = {
"height": this.dropdownsgridheight + 'px'
}
}
else {
this.finalHeight = '15px'
}
},
},
computed: {
dropdownsgridheight () {
return '50'
}
},
mounted() {
this.myEventHandler()
}
here is an example: https://jsfiddle.net/Nanif/yvw2h5pe/1/
To change whole row background color we can use getRowClass, but how to do the same logic only for one cell, and particular column....any ideas?
//EXTJS
viewConfig: {
getRowClass: function(record, index) {
var c = record.get('change');
if (c < 0) {
return 'price-fall';
} else if (c > 0) {
return 'price-rise';
}
}
}
//CSS
.price-fall {
background-color: #FFB0C4;
}
.price-rise {
background-color: #B0FFC5;
}
EDIT:
There is a way of doing this with:
function change(val){
if(val > 0){
return '<div class="x-grid3-cell-inner" style="background-color:#B0FFC5;"><span style="color:green;">' + val + '</span></div>';
}else if(val < 0){
return '<div class="x-grid3-cell-inner" style="background-color:#FFB0C4;"><span style="color:red;">' + val + '</span></div>';
}
return val || 0;
}
and then just:
...
{header: 'Change', width: 75, sortable: true, renderer: change, dataIndex: 'change', align: 'center'}
...
but this way grid gets deformed on changes from white to colored background... ???
any other ideas?
EDIT
After custom css is applyed to the column, how to remove the same in a short period of time, so it appears to blink once when the value has changed? Something like setTimeout("remove-css()",1000); or with Ext.Function.defer(remove-css, 1000);
Any ideas?
I suggest using getRowClass with applying extra cls to needed columns:
Ext.create('Ext.grid.Panel', {
columns: [
// ...
{ header: 'Change', dataIndex: 'change', tdCls: 'x-change-cell' },
// ...
],
viewConfig: {
getRowClass: function(record, index) {
var c = record.get('change');
if (c < 0) {
return 'price-fall';
} else if (c > 0) {
return 'price-rise';
}
}
},
// ...
});
CSS:
.price-fall .x-change-cell {
background-color: #FFB0C4;
color:red;
}
.price-rise .x-change-cell {
background-color: #B0FFC5;
color:green;
}
Here is demo.
There is also another method I found when I am doing another thing;
In column definition:
{
dataIndex: 'invoicePrintedFlag',
header: 'Fatura',
width: 50,
renderer : function(value, metadata, record) {
if (record.get('invoiceAddressId') != null){
metadata.tdCls = metadata.tdCls +" alertedCell";
}
return '<span class="iconbox icon-'+ value +'"></span>';
}
}
you can use renderer if you manipulate cell completely, here is comes metadata:
metadata: Object {tdCls: "", style: ""}
if you use style it will be added to content DIV inside TD
<td class=" x-grid-cell x-grid-cell-gridcolumn-1067" id="ext-gen1432">
<div unselectable="on" class="x-grid-cell-inner x-unselectable" style=" text-align: left;" id="ext-gen1426">
// Content comes here
</div>
</td>
if you use tdCls, it will be added to class attr of TD
<td class=" x-grid-cell x-grid-cell-gridcolumn-1067 alertedCell " id="ext-gen1462">
<div unselectable="on" class="x-grid-cell-inner x-unselectable" style="; text-align: left;" id="ext-gen1463">
// Content comes here
</div>
</td>
Also you can return html as you want.
renderer: function(value, metaData, record, rowIndex, colIndex, store, view) {
metaData.tdAttr = 'style="background-color:#b0e987;color:black;"';
value=Ext.util.Format.number(value, '$ 0,000.00');
return value;
},