How to make "ng-multiselect-dropdown" a required field in Anguar7? - css

How can I make ng-multiselect-dropdown a required field, which must accept at least one selected item ?
<ng-multiselect-dropdown
[placeholder]="'Select countries'"
[data]="countries"
[(ngModel)]="countriesSelectedItems"
[settings]="countriesDropdownSettings"
(onSelect)="onItemSelect($event)"
(onSelectAll)="onSelectAll($event)"
name="countries"
class="form-control">
</ng-multiselect-dropdown>

There was no documentation about it on npmjs.org
So, we take the approach we take to make any field as required in a template driven form in Angular.
relevant html:
<form (ngSubmit)='submission()'>
<ng-multiselect-dropdown [placeholder]="'custom placeholder'" [data]="dropdownList" [(ngModel)]="selectedItems" [settings]="dropdownSettings"
(onSelect)="onItemSelect($event)" (onSelectAll)="onSelectAll($event)" [required]='requiredField' [ngClass]='setClass()'
name='countrySelect'>
</ng-multiselect-dropdown>
<p *ngIf="!requiredField">
You must select a value !!
</p>
<br/>
<button type='submit'>submit</button>
</form>
relevant TS:
import { Component } from '#angular/core';
#Component({
selector: 'my-app',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css']
})
export class AppComponent {
name = 'Angular 5';
dropdownList = [];
selectedItems = [];
dropdownSettings = {};
requiredField: boolean = false;
ngOnInit() {
this.dropdownList = [
{ "item_id": 1, "item_text": "India" },
{ "item_id": 2, "item_text": "Singapore" },
{ "item_id": 3, "item_text": "Australia" },
{ "item_id": 4, "item_text": "Canada" },
{ "item_id": 5, "item_text": "Pakistan" },
{ "item_id": 6, "item_text": "Japan" }
];
this.selectedItems = [
{ "item_id": 2, "item_text": "Singapore" },
{ "item_id": 3, "item_text": "Australia" }
];
this.dropdownSettings = {
singleSelection: false,
idField: 'item_id',
textField: 'item_text',
selectAllText: 'Select All',
unSelectAllText: 'UnSelect All',
itemsShowLimit: 3,
allowSearchFilter: true
};
this.setStatus();
}
setStatus() {
(this.selectedItems.length > 0) ? this.requiredField = true : this.requiredField = false;
}
onItemSelect(item: any) {
//Do something if required
this.setClass();
}
onSelectAll(items: any) {
//Do something if required
this.setClass();
}
setClass() {
this.setStatus();
if (this.selectedItems.length > 0) { return 'validField' }
else { return 'invalidField' }
}
submission() {
if (this.requiredField == false) {
/* Print a message that not all required fields were filled... */
}
/* Do form submission... */
}
}
relevant css:
.validField { border:2px solid green; display: block; }
.invalidField { border:2px solid red; display: block; }
::ng-deep .multiselect-dropdown .dropdown-btn {width: -webkit-fill-available !important}
::ng-deep .multiselect-dropdown .dropdown-btn:focus {outline: none !important}
complete working stackblitz available here

Related

I'm not able to change the style of DevExtreme's popup modal dialog using CSS

I have Vue.js app where use DevExtreme's DxDataGrid and DxPopup components. The datagrid contains a list of records (rows). When I double click particular row, the popup modal dialog opens and shows appropriate fields. That's fine.
The issue is I'm not able to change the style of popup modal dialog using CSS selectors in my component. I need to change all - border, colors, size of fields, fonts and so on.
The code looks as following:
The list:
<template>
<div>
<DxDataGrid
:data-source="testArray"
:row-alternation-enabled="true"
:show-borders="true"
:columns="columns"
#row-dbl-click="rowDblClick"
>
</DxDataGrid>
<v-app-types-by-product-modal ref="appTypesByProductModal" />
</div>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxDataGrid } from "devextreme-vue/data-grid";
import VAppTypesByProductModal from "./v-app-types-by-product-modal.vue";
export default {
components: {
DxDataGrid,
VAppTypesByProductModal,
},
data() {
return {
testArray: [
{ Id: 1, ProductName: "Product 1", Status: "Yes", StageName: "Begin", SignDate: "11.07.2022" },
{ Id: 2, ProductName: "Product 2", Status: "Yes", StageName: "Middle", SignDate: "12.07.2022" },
{ Id: 3, ProductName: "Product 3", Status: "No", StageName: "End", SignDate: "13.07.2022" },
],
columns: [
{ dataField: "Id", caption: "ID", dataType: "number", width: "50px" },
{ dataField: "ProductName", caption: "PRODUCT", dataType: "string", width: "150px" },
{ dataField: "Status", caption: "STATUS", dataType: "string", width: "150px" },
{ dataField: "StageName", caption: "STAGE", dataType: "string", width: "150px"},
{ dataField: "SignDate", caption: "DATA", dataType: "string",width: "150px"},
{
type: "buttons",
width: "150px",
buttons: [
{
hint: "Edit",
icon: "edit",
visible(e) {
return !e.row.isEditing;
},
onClick: (e) => {
this.$refs.appTypesByProductModal.showModal(e.row.data);
},
},
{
hint: "Remove",
icon: "remove",
visible(e) {
return !e.row.isEditing;
},
},
],
},
],
selectedElement: {},
};
},
methods: {
rowDblClick(e) {
this.$refs.appTypesByProductModal.showModal(e.data);
},
},
};
</script>
<style scoped>
</style>
The modal dialog:
<template>
<v-modal-dialog
:visible="isModalVisible"
title="Title"
:width="500"
:height="500"
#hiding="onHiding"
>
<DxForm :form-data="selectedItem" label-mode="static">
<DxSimpleItem data-field="Id" :is-required="true" />
<DxSimpleItem data-field="ProductName" :is-required="true" :editor-options="{disabled: true}" />
<DxSimpleItem data-field="Status" />
<DxSimpleItem data-field="StageName" />
<DxSimpleItem data-field="SignDate" />
</DxForm>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="okButtonOptions"
/>
<DxToolbarItem
widget="dxButton"
toolbar="bottom"
:options="closeButtonOptions"
/>
</v-modal-dialog>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxForm, DxSimpleItem } from "devextreme-vue/form";
import { DxToolbarItem } from "devextreme-vue/popup";
import VModalDialog from "./v-modal-dialog.vue";
export default {
components: {
VModalDialog,
DxForm,
DxSimpleItem,
DxToolbarItem,
},
data() {
return {
isModalVisible: false,
selectedItem: {},
okButtonOptions: {
text: "Ok",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
closeButtonOptions: {
text: "Close",
onClick: () => {
this.clearFormData();
this.isModalVisible = false;
},
},
};
},
methods: {
showModal(item) {
this.selectedItem = {...item};
this.isModalVisible = true;
},
clearFormData() {
this.selectedItem = null;
},
onHiding() {
this.clearFormData();
this.isModalVisible = false;
},
},
};
</script>
<style scoped>
</style>
The modal dialog base:
<template>
<DxPopup
v-bind="$attrs"
v-on="$listeners"
:hide-on-outside-click="false"
:drag-enabled="true"
position="center"
#hiding="onHiding"
>
<slot v-for="(_, name) in $slots" :name="name" :slot="name" />
</DxPopup>
</template>
<script>
import "devextreme/dist/css/dx.light.css";
import { DxPopup } from "devextreme-vue/popup";
export default {
inheritAttrs: false,
components: {
DxPopup,
},
data() {
return {}
},
methods: {
onHiding() {
this.$emit("hiding");
},
},
};
</script>
<style scoped>
/* ::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
::v-deep .dx-popup-title {
border-bottom: 0px !important;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-toolbar {
border-bottom: 0px;
} */
/* #dxPopupContainer {
color: red;
background-color: green;
} */
/* ::v-deep .dx-overlay-content .dx-popup-normal .dx-popup-draggable .dx-resizable {
border-radius: 20px;
}
::v-deep .dx-toolbar > .dx-widget > dx-visibility-change-handler > dx-collection > dx-popup-title dx-has-close-button {
border-bottom: 0px;
} */
/* #dxPopupContainer .dx-popup-normal {
border-radius: 20px !important;
}
#dxPopupContainer .dx-popup-title {
border-bottom: 0px !important;
} */
</style>
Here's the CodeSandbox demo:
https://codesandbox.io/s/overview-devextreme-data-grid-forked-5cegqw
As you can see I've tried something, but nothing helped.
How to overcome this issue?
Replace
::v-deep .dx-popup-normal {
border-radius: 20px !important;
}
with
:global(.dx-popup-normal) {
border-radius: 20px !important;
}

vuetify: how to make v-data-table row blink when item values are updated

I would like to create a table that when items is updated, the row blink once.
i managed to make a row blink when the component starts, but it does not blink when value is updated.
i created an example with two css class (only for test), one that blinks once and another that blinks infinite.
if we update items values, we can see that the infinite still blinks and change rows as the condition is filled, but the items that should blink once, didn't change.
any help will be appreciated.
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (item.calories > 200){
return 'blink-great';
} else {
return 'blink-less';
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less {
animation: blinking ease-out 1s 3;
--background-color: #FF0000
}
.blink-great {
animation: blinking ease-out 1s infinite;
--background-color: #0000FF
}
#keyframes blinking {
0% {
background-color: var(--background-color);
}
100% {
background-color: #fff;
}
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
I created a flag and added new css classes.. it works but has poor code. If i found a better and clean solution i post it here
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
el: '#app',
vuetify: new Vuetify(),
data() {
return {
controlAnimation: false,
items: [{
id: 1,
name: 'Frozen Yogurt',
calories: 159,
},
{
id: 2,
name: 'Ice cream sandwich',
calories: 237,
},
{
id: 3,
name: 'Eclair',
calories: 262,
},
{
id: 4,
name: 'Cupcake',
calories: 305,
},
],
headers: [{
text: 'Dessert',
value: 'name',
},
{
text: 'Calories',
value: 'calories'
},
],
};
},
methods: {
blink(item) {
if (this.controlAnimation) {
let itemClassTrue = 'blink-even-true'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-true'
itemClassTrue = 'blink-even-true'
itemClassTrue = 'blink-great-true'
*/
return itemClassTrue;
} else {
let itemClassFalse = 'blink-great-false'
/* check item.value, based on condition...
itemClassTrue = 'blink-less-false'
itemClassTrue = 'blink-even-false'
itemClassTrue = 'blink-great-false'
*/
return itemClassFalse;
}
},
getRandomInt(min, max) {
min = Math.ceil(min);
max = Math.floor(max);
return Math.floor(Math.random() * (max - min)) + min;
},
updateValues() {
const newValues = this.items.map((el) => {
return {...el, calories: this.getRandomInt(100,500)};
})
this.items = newValues
}
},
computed: {
displayItems() {
this.controlAnimation = !this.controlAnimation
var newItems = this.items.map((el) => {
return {...el, calories: el.calories * 1};
})
return newItems
}
},
});
.blink-less-true {
animation: blinking-less-true 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-true {
animation: blinking-even-true 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-true {
animation: blinking-great-true 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
.blink-less-false {
animation: blinking-less-false 1s 2 !important;
--background-color: rgb(196, 76, 76) !important;
}
.blink-even-false {
animation: blinking-even-false 1s 2 !important;
--background-color: #fa0 !important;
}
.blink-great-false {
animation: blinking-great-false 1s 2 !important;
--background-color: rgb(3, 163, 70) !important;
}
#keyframes blinking-less-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-great-false {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-even-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
#keyframes blinking-less-true {
0% { background-color: var(--background-color); }
100% { background-color: #fff; }
}
<link href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/#mdi/font#4.x/css/materialdesignicons.min.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/vue#2.x/dist/vue.js"></script>
<script src="https://cdn.jsdelivr.net/npm/vuetify#2.x/dist/vuetify.js"></script>
<div id="app">
<v-app>
<v-container>
<v-row class="pa-5">
<v-btn
#click="updateValues()"
> Update value </v-btn>
</v-row>
<v-row class="px-5">
<v-data-table
hide-default-footer
:headers="headers"
:items="displayItems"
:item-class="blink"
/>
</v-row>
</v-container>
</v-app>
</div>
You're not far off with item-class. It can take a string, which tells it to look for a prop on the item's object. With this, as you're updating the data, you can just add/remove the blink class to a prop you create on the fly.
In this case, I'm just going to name the prop dynamicClass
something like this:
template:
<v-data-table
hide-default-footer
:headers="headers"
:items="items"
:item-class="dynamicClass"
/>
script:
methods: {
updateRandom() {
const randomIdx = this.generateRandomNumber(0, this.items.length - 1);
const randomNumber = this.generateRandomNumber(1, 1200);
let item = { ...this.items[randomIdx] };
item.calories = randomNumber;
item.dynamicClass = "blink-less";
this.$set(this.items, randomIdx, item);
setTimeout(() => {
delete item.dynamicClass;
this.$set(this.items, randomIdx, item);
}, 2000);
},
generateRandomNumber(min, max) {
return Math.floor(Math.random() * (max - min + 1) + min);
},
},
The idea here is that we are adding & removing the class from the item/object in the array. doing it with this.$set() guarantees reactivity. But you could also use Array.splice() but either way, you want to replace the data object at that index to maintain reactivity.
Here is a working example in a codesandbox

Vuedraggable how to swap item between 2 draggable

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>

Vue Carousel on iOS Safari only works with first slide

Good morning, I've build a custom vue carousel and implemented it into my progressive web app. Works fine on Chrome Android, whereas on iOS Safari - id doesn't. I received feedback that the carousel only allows to slide the first page and on the second - it doesn't react.
The problem is, I don't own Mac or Iphone and for now I can't test it myself. Could someone help me out and test the carousel on aforementioned devices? Here is the fiddle.
https://jsfiddle.net/LSliwa/97cpgq3z/
HTML:
<div id="app">
<Carousel>
<div v-for="(todo, index) in todos" :key="index">
<div class="app__element">
{{ todo.text }}
</div>
</div>
</Carousel>
</div>
CSS:
#app {
padding-top: 1rem;
}
.app__element {
text-align: center;
border: 1px solid red;
padding: 1rem;
margin: 0 1rem;
}
/* carousel styles */
.carousel {
overflow-x: hidden;
overflow-y: visible;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.carousel-navdots {
display: flex;
justify-content: center;
margin-bottom: 2rem;
}
.carousel-navdot {
height: 10px;
width: 10px;
border-radius: 50%;
background-color: gray;
margin: 0 5px;
transition: all 0.5s;
cursor: pointer;
}
.active {
background-color: #05AA19;
}
.carousel-wrapper {
display: flex;
align-items: stretch;
cursor: grab;
}
.carousel-wrapper:active {
cursor: grabbing;
}
.carousel-wrapper>div,
.carousel-wrapper>p,
.carousel-wrapper>span,
.carousel-wrapper>ul {
width: 100%;
flex-shrink: 0;
position: relative;
}
.scrolling {
transition: transform 0.5s;
}
.inactive {
flex-direction: column;
}
#media (min-width: 1024px) {
.inactive {
flex-direction: row;
flex-wrap: wrap;
justify-content: flex-start;
}
}
JS:
Vue.component('Carousel', {
template: '<div class="carousel"><div class="carousel-navdots" v-if="isActive" v-show="pagination"><div class="carousel-navdot" :class="{ \'active\': n == currentPage + 1 }" v-for="n in numberOfPages" v-show="numberOfPages > 1" :key="n" ref="navdot" #click="scrollWithNavdots(n)"></div></div><div class="carousel-wrapper a-stretch" :class="{ \'inactive\': !isActive }" :style="{ transform: `translateX(${translate}px)` }" ref="wrapper" v-on="isActive && active ? { touchstart: onTouchStart, touchmove: onTouchMove, touchend: onTouchEnd, mousedown: onTouchStart, mousemove: onTouchMove, mouseup: onTouchEnd } : {}"><slot></slot></div></div>',
props: {
active: {
type: Boolean,
default: () => true
},
activeOnViewport: {
type: Array,
default: () => [[1, true]]
},
columns: {
type: Array,
default: () => [[1, 1]]
},
pagination: {
type: Boolean,
default: () => true
},
sensitivity: {
type: Number,
default: () => 40
},
startFromPage: {
type: Number,
default: () => 0
},
autoplay: {
type: Boolean,
default: () => false
},
autoplaySpeed: {
type: Number,
default: () => 5
},
// usuń custom length jeżeli rerendering z :key zadziała
customLength: {
type: Number
}
},
data() {
return {
currentPage: this.startFromPage,
numberOfColumns: 1,
moveStart: null,
move: null,
currentTranslate: 0,
length: this.customLength == null ? this.$slots.default.length : this.customLength,
viewportColumnsMatched: null,
isActive: null,
mousedown: false,
elementWidth: 0,
autoplayInterval: null,
animateTimeout: null,
test: {
touchmove: false,
touchstart: false,
}
}
},
computed: {
maxScrollLeft() {
return this.currentPage == 0 ? true : false;
},
maxScrollRight() {
return this.currentPage + 1 == this.numberOfPages ? true : false;
},
numberOfPages() {
return Math.ceil(this.length / this.numberOfColumns);
},
sortedColumns() {
return this.columns.sort((a, b) => {
return a[0] - b[0];
});
},
sortedActive() {
return this.activeOnViewport.sort((a, b) => {
return a[0] - b[0];
})
},
translate() {
return -this.elementWidth * this.currentPage
}
},
watch: {
currentPage() {
this.animateCarousel();
this.$emit('change-page', this.currentPage);
},
startFromPage() {
this.currentPage = this.startFromPage;
},
// usuń watch customLength jeżeli rerendering z :key zadziała
customLength() {
this.length = this.customLength;
if (this.currentPage > this.length - 1) {
this.currentPage = this.length - 1;
}
}
},
methods: {
animateCarousel() {
this.$refs.wrapper.classList.add('scrolling');
this.animateTimeout = setTimeout(() => {
this.$refs.wrapper.classList.remove('scrolling');
}, 500);
},
scrollWithNavdots(index) {
this.currentPage = index - 1;
this.currentTranslate = parseFloat(this.$refs.wrapper.style.transform.slice(11, -3));
},
onTouchStart() {
clearInterval(this.autoplayInterval);
if (event.type == 'touchstart') {
this.moveStart = event.touches[0].screenX
} else {
this.moveStart = event.screenX;
this.mousedown = true;
}
},
onTouchMove() {
let translate;
if (event.type == 'touchmove') {
this.move = event.touches[0].screenX - this.moveStart;
} else if (event.type == 'mousemove' && this.mousedown == true) {
this.move = event.screenX - this.moveStart
}
if (this.move < 0 && this.maxScrollRight || this.move > 0 && this.maxScrollLeft) {
translate = this.translate + this.move*0.2;
} else {
translate = this.translate + this.move*0.5;
}
this.$refs.wrapper.style.transform = `translateX(${translate}px)`;
},
onTouchEnd() {
this.test.touchstart = false;
this.test.touchmove = false;
if (Math.abs(this.move) > this.sensitivity) {
if (this.move > 0 && !this.maxScrollLeft) {
this.currentPage--
} else if (this.move < 0 && !this.maxScrollRight) {
this.currentPage++
} else {
this.animateCarousel();
}
} else if (Math.abs(this.move) < this.sensitivity && Math.abs(this.move) > 1) {
this.animateCarousel();
}
this.$refs.wrapper.style.transform = `translateX(${this.translate}px)`;
this.mousedown = false;
this.moveStart = null;
this.move = null;
},
setColumns() {
this.viewportColumnsMatched = false;
this.sortedColumns.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.viewportColumnsMatched = true;
this.numberOfColumns = cur[1];
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = `${100/this.numberOfColumns}%`;
});
}
});
if (!this.viewportColumnsMatched) {
this.numberOfColumns = 1;
this.$refs.wrapper.childNodes.forEach(cur => {
cur.style.width = '100%';
});
}
setTimeout(() => {
this.elementWidth = this.$slots.default[0].elm.offsetWidth;
});
},
setActive() {
this.sortedActive.forEach(cur => {
if (window.matchMedia(`(min-width: ${cur[0]}px)`).matches) {
this.isActive = cur[1];
}
});
},
runCarousel() {
if (this.autoplay) {
this.autoplayInterval = setInterval(() => {
this.currentPage++
if (this.currentPage == this.numberOfPages) this.currentPage = 0;
}, this.autoplaySpeed * 1000);
}
},
},
mounted() {
this.setColumns();
this.setActive();
this.runCarousel();
window.addEventListener('resize', () => {
this.setColumns();
this.setActive();
});
},
destroyed() {
clearInterval(this.autoplayInterval);
clearTimeout(this.animateTimeout);
}
});
new Vue({
el: "#app",
data: {
todos: [
{ text: "Learn JavaScript", done: false },
{ text: "Learn Vue", done: false },
{ text: "Play around in JSFiddle", done: true },
{ text: "Build something awesome", done: true }
]
},
})
Thank you in advance for your help, good people.

How to grey out the font as well as the checkbox

I would like to grey out the text within my filter section of a calendar app I am building but only seem to be able to code this onto the checkbox itself.
I have already tried passing this through my SCSS containers and labels with the ~ syntax to pass done from parent to child selectors. Cannot seem to find a way to solve this without breaking DRY principles. I have managed to get this to work on the checkbox but not the font within the same container.
Checkbox/index.js
import React from 'react'
import './index.css';
import { FontAwesomeIcon } from '#fortawesome/react-fontawesome'
import PropTypes from 'prop-types'
/*
based off the fake checkbox styles from here
should still be screen-reader/keyboard accessible
https://www.w3schools.com/howto/howto_css_custom_checkbox.asp
*/
class Checkbox extends React.Component {
render() {
const { name, label, value, checked, onChange, handleClose, dataObject, ...rest } = this.props
return (
<div {...rest}>
<label className="checkbox-container">
<span className="checkbox-label">{label}</span>
<input
type="checkbox"
checked={checked}
value={value}
onChange={onChange} />
<span className="checkmark"></span>
{this.props.handleClose && <FontAwesomeIcon onClick={this.handleClick.bind(this)} className='checkbox-icon' size="1x" icon={['fal', 'times']} />}
</label>
</div>
)
}
handleClick(e) {
e.preventDefault()
this.props.handleClose(this.props.dataObject)
}
}
Checkbox.propTypes = {
handleClose: PropTypes.func, // optional
onChange: PropTypes.func.isRequired,
label: PropTypes.string.isRequired,
value: PropTypes.oneOfType([
PropTypes.string,
PropTypes.number
]).isRequired,
dataObject: PropTypes.object, // optional -> used for the 'x' button
checked: PropTypes.bool.isRequired,
}
export default Checkbox
CalendersFiltersSection/index.js
import React from 'react'
import './index.css';
import PropTypes from 'prop-types'
import Checkbox from '../../../Checkbox'
import { defaultCalendarFilters} from '../../../../../constants'
class CalendarFiltersSection extends React.Component {
render() {
const { filters } = this.props
// let orderedFilters = Object.values(filters)
// orderedFilters.sort((a,b) => {
// return a.displayOrder - b.displayOrder
// })
return (
<section className="calendar-filters-section">
<div className='title'>{this.props.title}</div>
{ Object.values(filters).map(filter => {
let className = defaultCalendarFilters[filter.id].label.toLowerCase()
return <Checkbox className={`checkbox-event-type-${className}`} key={filter.id} label={filter.label} name={filter.id} checked={filter.checked} value={filter.id} onChange={this.props.handleInputChange} />
})
}
</section>
)
}
}
CalendarFiltersSection.propTypes = {
handleInputChange: PropTypes.func.isRequired,
filters: PropTypes.object.isRequired,
title: PropTypes.string.isRequired
}
export default CalendarFiltersSection
Checkbox/index.css
.checkbox-container {
display: block;
position: relative;
padding-left: 26px;
margin-bottom: 8px;
cursor: pointer;
font-size: 14px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default checkbox */
.checkbox-container input {
position: absolute;
opacity: 0;
cursor: pointer;
height: 0;
width: 0;
}
/* Create a custom checkbox */
.checkmark {
position: absolute;
top: 0;
left: 0;
height: 18px;
width: 18px;
background-color: #525259;
}
/* On mouse-over, add a grey background color */
/*.checkbox-container:hover input ~ .checkmark {
background-color: #ccc;
}
*/
/* When the checkbox is checked, add a blue background */
/*.checkbox-container input:checked ~ .checkmark {
background-color: #2196F3;
}*/
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: "";
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.checkbox-container input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.checkbox-container .checkmark:after {
left: 7px;
top: 3px;
width: 3px;
height: 7px;
border: solid black;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
box-sizing: initial;
color: var(--secondary-font-color);
}
.checkbox-event-type-trainings .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-trainings-color);
}
.checkbox-event-type-medizinisch .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-medizinisch-color);
}
.checkbox-event-type-spiele .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-spiele-color);
}
.checkbox-event-type-tests .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-tests-color);
}
.checkbox-event-type-andere .checkbox-container input:checked ~ .checkmark {
background-color: var(--event-type-andere-color);
}
.checkbox-label {
color: var(--secondary-font-color);
}
.checkbox-icon {
position:absolute;
right: 0;
top: 4px;
z-index:5;
}
.checkbox-container .checkbox-icon {
visibility: hidden;
}
.checkbox-container:hover .checkbox-icon {
visibility: visible;
}
CalendarFiltersSection/index.css
.calendar-filters-section {
align-self: flex-start;
color: var(--secondary-font-color);
margin-bottom: 4rem;
}
Constants.js
import { history } from './util/history'
export const API_HOST = process.env.REACT_APP_USE_STAGING_API ?
"http://example.com" : "http://127.0.0.1:8000"
export const EVENTS_LIST_URL = `${API_HOST}/events/`
export const CALENDARS_LIST_URL = `${API_HOST}/users/calendars/`
export const USERS_LIST_URL = `${API_HOST}/users/`
export const getTeamsListUrl = (clubId) =>
`${API_HOST}/clubs/${clubId}/teams/`
export const getClubsTeamsPlayersListUrl = (clubId, teamId) =>
`${API_HOST}/clubs/${clubId}/teams/${teamId}/players/`
//TODO combine all of these into one data structure .
export const mapNameToEventTypeNumber = {
0: 'andere',
1: 'trainings',
2: 'tests',
3: 'medizinisch',
4: 'medizinisch',
5: 'medizinisch',
6: 'spiele',
8: 'medizinisch',
}
//TODO combine all of these into one data structure .
// updaet: the backend updated to use strings instead of numbers...
export const mapMainCategoryNameToSubCategoryName = {
social: 'andere',
practice: 'trainings',
assessment: 'tests',
doctor: 'medizinisch',
physio: 'medizinisch',
operation: 'medizinisch',
match: 'spiele',
meeting: 'andere',
}
// 'event_types': ((0, 'SOCIAL'),
// (1, 'PRACTICE'),
// (2, 'ASSESSMENT'),
// (3, 'DOCTOR'),
// (4, 'PHYSIO'),
// (5, 'OPERATION'),
// (6, 'MATCH'),
// (7, 'PRIVATE'),
// (8, 'MEETING'),
// (9, 'SCHOOL/JOB'),
// (10, 'MISC')),
export const mapSpecificEventTypeCategoryToEventTypeNumber = {
0: 'Social',
1: 'Practice',
2: 'Examination',
3: 'Doctor',
4: 'Rehab',
5: 'Physio',
6: 'Match',
7: 'Injury',
8: 'Treatment',
}
export const mappingOfSpecificEventTypeToFilterCategories = {
1: 0,
3: 1,
4: 1,
5: 1,
6: 2,
2: 3,
0: 4
}
export const defaultCalendarFilters = [
{
id: 0,
checked: true,
label: 'Trainings',
eventTypeNumbers: ['practice']
// eventTypeNumbers: ['1']
},
{
id: 1,
checked: true,
label: 'Medizinisch',
eventTypeNumbers: ['doctor','physio','operation']
// eventTypeNumbers: ['3','4','5']
},
{
id: 2,
checked: true,
label: 'Spiele',
eventTypeNumbers: ['match']
// eventTypeNumbers: ['6']
},
{
id: 3,
checked: true,
label: 'Tests',
eventTypeNumbers: ['assessment']
// eventTypeNumbers: ['2']
},
{
id: 4,
checked: true,
label: 'Andere',
eventTypeNumbers: ['meeting', 'misc']
// eventTypeNumbers: ['0']
},
]
export const mapClubRoleNumberToSlug = {
'trainer': 0,
'athletiktrainer': 1,
'torwarttrainer': 2,
'individualtrainer': 3,
'rehabilitationtrainer': 4,
'arzt': 5,
'physiotherapeut': 6,
'videoanalyst': 7,
'athlet': 8,
'betreuer': 9,
'clubadmin': 10,
}
export const clubRolesData = {
'trainer': {
slug: 'trainer',
roleNum: 0,
isStaff: true,
isAthlete: false
},
'athletiktrainer': {
slug: 'athletiktrainer',
roleNum: 1,
isStaff: true,
isAthlete: false
},
'torwarttrainer': {
slug: 'torwarttrainer',
roleNum: 2,
isStaff: true,
isAthlete: false
},
'individualtrainer': {
slug: 'individualtrainer',
roleNum: 3,
isStaff: true,
isAthlete: false
},
'rehabilitationtrainer': {
slug: 'rehabilitationtrainer',
roleNum: 4,
isStaff: true,
isAthlete: false
},
'arzt': {
slug: 'arzt',
roleNum: 5,
isStaff: true,
isAthlete: false
},
'physiotherapeut': {
slug: 'physiotherapeut',
roleNum: 6,
isStaff: true,
isAthlete: false
},
'videoanalyst': {
slug: 'videoanalyst',
roleNum: 7,
isStaff: true,
isAthlete: false
},
'athlet': {
slug: 'athlet',
roleNum: 8,
isStaff: false,
isAthlete: true
},
'betreuer': {
slug: 'betreuer',
roleNum: 9,
isStaff: true,
isAthlete: false
},
'clubadmin': {
slug: 'clubadmin',
roleNum: 10,
isStaff: true,
isAthlete: false
},
}
export const ATHLETE_ROLES = [
'athlet'
]
export const STAFF_ROLES = [
'arzt',
'athletiktrainer',
'betreuer',
// 'clubadmin',
'individualtrainer',
'physiotherapeut',
'rehabilitationtrainer',
'torwarttrainer',
'trainer',
'videoanalyst',
]
export const getUserData = () => {
let userData = null
const userDataJson = localStorage.userData
if(userDataJson) {
userData = JSON.parse(userDataJson)
} else {
history.push('./login')
return
}
return userData
}
export const NAVBAR_TABS = [
{
text: 'Dein Club',
link: '/club-member',
active: false,
icon: 'fa-shield'
},
{
text: 'Kalender',
link: '/calendar',
active: false,
icon: 'fa-calendar-alt'
},
{
text: 'Library',
link: '/library/abilities',
active: false,
icon: 'fa-books'
}
]

Resources