I tried zebra striping my table in a React application here. However, it is not being applied. Here are the relevant styles:
tr:nth-child(even) {
background-color: rgba(0,100,1,0.5)
}
tr:nth-child(odd) {
background-color: rgba(0,100,1,0.3)
}
And the full demo:
'use strict';
var /*var Leaderboard = React.createClass({
getInitialState: function(){
/*var recentCampers= [];
var allTimeCampers= [];*/
//return {toggle30: 'true'};
/*return {recentCampers: [],
allTimeCampers: [],
toggle30: 'true'};
this.recentCampers= this.recentCampers.bind(this);
this.allTimeCampers.bind(this);
this.toggle30= this.toggle30.bind(this); */
/* },
componentDidMount: function(){return axios.all([this.getTopRecentCampers(),this.getTopAlltimeCampers()]).then(function(arr){return {
recentCampers: arr[0].data,
allTimeCampers: arr[1].data};
console.log(this.state.recentCampers[0].username); })},
/*
getTopRecentCampers: function(){return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/recent');},
getTopAlltimeCampers: function(){return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/alltime');},
toggleViewOne: function(){this.setState({toggle30: 'true'});},
toggleViewTwo: function(){this.setState({toggle30: 'false'});},
/* render: function(){return (<div>
<h6>Sort by:</h6> <button onClick={this.toggleViewOne} className="btn">Points in past 30 days <span className="fa fa-sort-desc"> </span></button> <button onClick={this.toggleViewTwo} className="btn"> All time points <span className="fa fa-sort-desc"> </span> </button> <h1> freeCodeCamp <span className="fa fa-free-code-camp"> </span> </h1> <hr></hr> <div className="table-responsive"> <table className="table"> <thead> <tr> <th> <td> # </td> <td> Camper name </td> <td> Points in last month </td> <td> All time points </td> </th> </tr> </thead> <tbody>
//</tbody> </table> </div> </div>);} */
/* });
var Tbody = React.createClass({
render: function(){return (<tbody> {} </tbody>);}
});*/
/*</tr> <tr> <td> gtew</td> </tr> <tr> <td> gffs</td> </tr>this.recentCampers= this.recentCampers.bind(this);
this.allTimeCampers.bind(this) */
/*
ReactDOM.render(<Leaderboard />,document.getElementById('freeCodeCamp'));
---------------------------------------------------------------------------- */Leaderboard = React.createClass({
displayName: 'Leaderboard',
getInitialState: function getInitialState() {
return {
recentCampers: [],
allTimeCampers: [],
toggle: 'recentCampers'
};
},
componentWillMount: function componentWillMount() {
return this.getRequest();
},
getRequest: function getRequest() {
return axios.all([this.getTopRecentCampers(), this.getTopAlltimeCampers()]).then(function (arr) {
this.setState({
recentCampers: arr[0].data,
allTimeCampers: arr[1].data
});
/*var recent= [];
recent= this.state.recentCampers;
for (var i=0;i>=100;i++){return recent[i];};
//console.log(this.state.recentCampers[0].username);*/
}.bind(this));
},
getTopRecentCampers: function getTopRecentCampers() {
return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/recent');
},
getTopAlltimeCampers: function getTopAlltimeCampers() {
return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/alltime');
},
toggleViewOne: function toggleViewOne() {
this.setState({
toggle: 'recentCampers'
});
},
toggleViewTwo: function toggleViewTwo() {
this.setState({
toggle: 'allTimeCampers'
});
},
render: function render() {
return React.createElement(
'div',
null,
' ',
' ',
React.createElement(
'h6',
null,
'Sort by:'
),
' ',
React.createElement(
'button',
{ onClick: this.toggleViewOne, className: 'btn' },
'Points in past 30 days ',
React.createElement(
'span',
{ className: 'fa fa-sort-desc' },
' '
)
),
' ',
React.createElement(
'button',
{ onClick: this.toggleViewTwo, className: 'btn' },
' All time points ',
React.createElement(
'span',
{ className: 'fa fa-sort-desc' },
' '
),
' '
),
' ',
React.createElement(
'h1',
null,
' freeCodeCamp ',
React.createElement(
'span',
{ className: 'fa fa-free-code-camp' },
' '
),
' '
),
' ',
React.createElement('hr', null),
' ',
React.createElement(
'div',
{ className: 'table-responsive' },
' ',
React.createElement(
'table',
{ className: 'table' },
' ',
React.createElement(
'thead',
null,
' ',
React.createElement(
'tr',
null,
' ',
React.createElement(
'th',
null,
' ',
React.createElement(
'td',
{ style: { width: '80px' }, className: 'text-center' },
' # '
),
' ',
React.createElement(
'td',
{ style: { width: '600px' } },
' Camper name '
),
' ',
React.createElement(
'td',
{ style: { width: '400px' }, className: 'text-center' },
' Points in last month '
),
' ',
React.createElement(
'td',
{ style: { width: '400px' }, className: 'text-center' },
' All time points '
),
' '
),
' '
),
' '
),
' ',
React.createElement(
'tbody',
null,
' ',
React.createElement(Map, { data: this.state[this.state.toggle] })
),
' '
),
' '
),
' '
);
}
});
//{<Tbody data={this.state}
/*
-------------------------------------------------------------------------- */
var Map = React.createClass({
displayName: 'Map',
render: function render() {
var rows = this.props.data.map(function (row, index) {
return React.createElement(Tbody, { rank: index + 1, data: row });
});
return React.createElement(
'tbody',
{ id: 'stripe' },
rows
);
}
});
/*
-------------------------------------------------------------------------- */
var Tbody = React.createClass({
displayName: 'Tbody',
render: function render() {
return React.createElement(
'div',
null,
' ',
/*<h1>{this.props.data.username}</h1>*/React.createElement(
'tr',
null,
React.createElement(
'td',
{ style: { width: '80px' }, className: 'text-center' },
this.props.rank + '.'
),
React.createElement(
'td',
{ style: { width: '600px' } },
React.createElement(
'a',
{ target: '_blank', href: 'http://freecodecamp.com/' + this.props.data.username },
React.createElement('img', { src: this.props.data.img }),
' ',
React.createElement(
'span',
null,
this.props.data.username
)
)
),
React.createElement(
'td',
{ id: 'recent', className: 'text-center' },
this.props.data.recent
),
React.createElement(
'td',
{ id: 'all', className: 'text-center' },
this.props.data.alltime
)
),
' '
);
//console.log(JSON.stringify(this.props));*/
}
});
/*
-------------------------------------------------------------------------- */
ReactDOM.render(React.createElement(Leaderboard, null), document.getElementById('freeCodeCamp'));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-image: linear-gradient(161deg, #006401 0%, #FCFFEE 100%);
background-attachment: fixed;
color: #502d17;
margin-left: 20px;
margin-right: 20px;
}
button {
margin: 5px;
background-image: -webkit-linear-gradient(#006401, #FCFFEE);
background-attachment: fixed;
color: #FCFFEE;
box-shadow: inset -1px -3px 10px 1px #515151;
margin-bottom: 20px;
}
button:active {
transform: translate(0, 3px);
box-shadow: none;
text-decoration: none;
outline: none;
}
button:hover, button:active, button:visited {
text-decoration: none;
outline: none;
}
h6 {
margin-left: 10px;
margin-top: 15px;
margin-bottom: 5px;
}
h1 {
color: rgba(245, 245, 245, 0.5);
margin-left: 5px;
}
.fa-free-code-camp {
font-size: 1em;
color: rgba(245, 245, 245, 0.7);
}
table, td {
border: 1px solid grey;
table-layout: fixed;
}
tr:nth-child(even) {
background-color: rgba(0, 100, 1, 0.5);
}
tr:nth-child(odd) {
background-color: rgba(0, 100, 1, 0.3);
}
img {
border-radius: 100%;
height: 60px;
}
a {
text-decoration: none;
color: #502d17;
}
#all {
max-width: 280px;
min-width: 280px;
}
#recent {
max-width: 280px;
min-width: 280px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.8.1/axios.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<div id="freeCodeCamp"></div>
As your HTML markup is pulled in within your codepen I'm not sure how useful this is to future users but in the context of your codepen your selectors are incorrect.
Your tbody does not contain tr siblings but each tr is wrapped into a div, hence you need to target those instead.
tbody>div:nth-child(even)
background-color: rgba(0,100,1,0.5)
tbody>div:nth-child(odd)
background-color: rgba(0,100,1,0.3)
See updated CodePen
'use strict';
var /*var Leaderboard = React.createClass({
getInitialState: function(){
/*var recentCampers= [];
var allTimeCampers= [];*/
//return {toggle30: 'true'};
/*return {recentCampers: [],
allTimeCampers: [],
toggle30: 'true'};
this.recentCampers= this.recentCampers.bind(this);
this.allTimeCampers.bind(this);
this.toggle30= this.toggle30.bind(this); */
/* },
componentDidMount: function(){return axios.all([this.getTopRecentCampers(),this.getTopAlltimeCampers()]).then(function(arr){return {
recentCampers: arr[0].data,
allTimeCampers: arr[1].data};
console.log(this.state.recentCampers[0].username); })},
/*
getTopRecentCampers: function(){return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/recent');},
getTopAlltimeCampers: function(){return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/alltime');},
toggleViewOne: function(){this.setState({toggle30: 'true'});},
toggleViewTwo: function(){this.setState({toggle30: 'false'});},
/* render: function(){return (<div>
<h6>Sort by:</h6> <button onClick={this.toggleViewOne} className="btn">Points in past 30 days <span className="fa fa-sort-desc"> </span></button> <button onClick={this.toggleViewTwo} className="btn"> All time points <span className="fa fa-sort-desc"> </span> </button> <h1> freeCodeCamp <span className="fa fa-free-code-camp"> </span> </h1> <hr></hr> <div className="table-responsive"> <table className="table"> <thead> <tr> <th> <td> # </td> <td> Camper name </td> <td> Points in last month </td> <td> All time points </td> </th> </tr> </thead> <tbody>
//</tbody> </table> </div> </div>);} */
/* });
var Tbody = React.createClass({
render: function(){return (<tbody> {} </tbody>);}
});*/
/*</tr> <tr> <td> gtew</td> </tr> <tr> <td> gffs</td> </tr>this.recentCampers= this.recentCampers.bind(this);
this.allTimeCampers.bind(this) */
/*
ReactDOM.render(<Leaderboard />,document.getElementById('freeCodeCamp'));
---------------------------------------------------------------------------- */Leaderboard = React.createClass({
displayName: 'Leaderboard',
getInitialState: function getInitialState() {
return {
recentCampers: [],
allTimeCampers: [],
toggle: 'recentCampers'
};
},
componentWillMount: function componentWillMount() {
return this.getRequest();
},
getRequest: function getRequest() {
return axios.all([this.getTopRecentCampers(), this.getTopAlltimeCampers()]).then(function (arr) {
this.setState({
recentCampers: arr[0].data,
allTimeCampers: arr[1].data
});
/*var recent= [];
recent= this.state.recentCampers;
for (var i=0;i>=100;i++){return recent[i];};
//console.log(this.state.recentCampers[0].username);*/
}.bind(this));
},
getTopRecentCampers: function getTopRecentCampers() {
return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/recent');
},
getTopAlltimeCampers: function getTopAlltimeCampers() {
return axios.get('https://fcctop100.herokuapp.com/api/fccusers/top/alltime');
},
toggleViewOne: function toggleViewOne() {
this.setState({
toggle: 'recentCampers'
});
},
toggleViewTwo: function toggleViewTwo() {
this.setState({
toggle: 'allTimeCampers'
});
},
render: function render() {
return React.createElement(
'div',
null,
' ',
' ',
React.createElement(
'h6',
null,
'Sort by:'
),
' ',
React.createElement(
'button',
{ onClick: this.toggleViewOne, className: 'btn' },
'Points in past 30 days ',
React.createElement(
'span',
{ className: 'fa fa-sort-desc' },
' '
)
),
' ',
React.createElement(
'button',
{ onClick: this.toggleViewTwo, className: 'btn' },
' All time points ',
React.createElement(
'span',
{ className: 'fa fa-sort-desc' },
' '
),
' '
),
' ',
React.createElement(
'h1',
null,
' freeCodeCamp ',
React.createElement(
'span',
{ className: 'fa fa-free-code-camp' },
' '
),
' '
),
' ',
React.createElement('hr', null),
' ',
React.createElement(
'div',
{ className: 'table-responsive' },
' ',
React.createElement(
'table',
{ className: 'table' },
' ',
React.createElement(
'thead',
null,
' ',
React.createElement(
'tr',
null,
' ',
React.createElement(
'th',
null,
' ',
React.createElement(
'td',
{ style: { width: '80px' }, className: 'text-center' },
' # '
),
' ',
React.createElement(
'td',
{ style: { width: '600px' } },
' Camper name '
),
' ',
React.createElement(
'td',
{ style: { width: '400px' }, className: 'text-center' },
' Points in last month '
),
' ',
React.createElement(
'td',
{ style: { width: '400px' }, className: 'text-center' },
' All time points '
),
' '
),
' '
),
' '
),
' ',
React.createElement(
'tbody',
null,
' ',
React.createElement(Map, { data: this.state[this.state.toggle] })
),
' '
),
' '
),
' '
);
}
});
//{<Tbody data={this.state}
/*
-------------------------------------------------------------------------- */
var Map = React.createClass({
displayName: 'Map',
render: function render() {
var rows = this.props.data.map(function (row, index) {
return React.createElement(Tbody, { rank: index + 1, data: row });
});
return React.createElement(
'tbody',
{ id: 'stripe' },
rows
);
}
});
/*
-------------------------------------------------------------------------- */
var Tbody = React.createClass({
displayName: 'Tbody',
render: function render() {
return React.createElement(
'div',
null,
' ',
/*<h1>{this.props.data.username}</h1>*/React.createElement(
'tr',
null,
React.createElement(
'td',
{ style: { width: '80px' }, className: 'text-center' },
this.props.rank + '.'
),
React.createElement(
'td',
{ style: { width: '600px' } },
React.createElement(
'a',
{ target: '_blank', href: 'http://freecodecamp.com/' + this.props.data.username },
React.createElement('img', { src: this.props.data.img }),
' ',
React.createElement(
'span',
null,
this.props.data.username
)
)
),
React.createElement(
'td',
{ id: 'recent', className: 'text-center' },
this.props.data.recent
),
React.createElement(
'td',
{ id: 'all', className: 'text-center' },
this.props.data.alltime
)
),
' '
);
//console.log(JSON.stringify(this.props));*/
}
});
/*
-------------------------------------------------------------------------- */
ReactDOM.render(React.createElement(Leaderboard, null), document.getElementById('freeCodeCamp'));
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
background-image: linear-gradient(161deg, #006401 0%, #FCFFEE 100%);
background-attachment: fixed;
color: #502d17;
margin-left: 20px;
margin-right: 20px;
}
button {
margin: 5px;
background-image: -webkit-linear-gradient(#006401, #FCFFEE);
background-attachment: fixed;
color: #FCFFEE;
box-shadow: inset -1px -3px 10px 1px #515151;
margin-bottom: 20px;
}
button:active {
transform: translate(0, 3px);
box-shadow: none;
text-decoration: none;
outline: none;
}
button:hover, button:active, button:visited {
text-decoration: none;
outline: none;
}
h6 {
margin-left: 10px;
margin-top: 15px;
margin-bottom: 5px;
}
h1 {
color: rgba(245, 245, 245, 0.5);
margin-left: 5px;
}
.fa-free-code-camp {
font-size: 1em;
color: rgba(245, 245, 245, 0.7);
}
table, td {
border: 1px solid grey;
table-layout: fixed;
}
tbody>div:nth-child(even){
background-color: rgba(0, 100, 1, 0.5);
}
tbody>div:nth-child(odd) {
background-color: rgba(0, 100, 1, 0.3);
}
img {
border-radius: 100%;
height: 60px;
}
a {
text-decoration: none;
color: #502d17;
}
#all {
max-width: 280px;
min-width: 280px;
}
#recent {
max-width: 280px;
min-width: 280px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.8.1/axios.min.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet"/>
<link href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css" rel="stylesheet"/>
<div id="freeCodeCamp"></div>
Just to add, while the above works, I think your JavaScript generates invalid HTML to begin with as I think a div is not allowed as a child inside a tbody wrapping tr elements. If this is your own code, you can update it to use multiple tbody siblings inside the table element, each containing tr elements, instead of nested tbody elements with a div wrapping tr elements. If this is not your own code but the code you got from freeCodeCamp, I would consider switching code camp.
Your table markup is invalid. Your rows shouldn't be wrapped in divs.
The reason nth-child isn't working in this case is because it can only iterate through sibling elements(https://www.w3.org/TR/css3-selectors/#nth-child-pseudo).
Remove your wrapping divs in Leaderboard render function and it should work just fine.
Related
I am implementing a customized dropdown becuase of the requirements we have, using Vue 2 and typescript (jquery is not an option).
It is working fine, when you click on the main box, it opens the options list downwards.
An improvement I am looking for is that, when at the end of the screen, the options list adds to the page height and thus causing the scrollbar to appear or increase scroll height.
What I am looking for is that, when popping up the div, if there's not enough space at the bottom of the screen, open it upwards instead. How do I achieve this?
(classes are using bootstrat 5)
Opened dropdown &
Closed dropdown
My code:
import Vue, {
PropType
} from 'vue';
import {
Validation
} from 'vuelidate';
let uidc = 0;
export default Vue.extend({
name: 'BaseDropdown',
props: {
value: {
type: [Number, String, Object],
default: () => ''
as string,
},
target: {
type: String,
default: '',
},
label: {
type: String,
default: '',
},
valueIsNumber: {
type: Boolean,
default: false,
},
options: {
type: Array,
default: null,
},
placeholder: {
type: String,
default: '',
},
required: {
type: Boolean,
default: false,
},
disabled: {
type: Boolean,
default: false,
},
validations: {
type: Object as PropType < Validation > ,
default: () => ({
$error: false,
$touch: () => undefined,
$params: {},
}) as Validation,
},
error: {
type: Boolean,
default: false,
},
trackEvent: {
type: String,
default: '',
},
trackField: {
type: String,
default: '',
},
trackPublic: {
type: Boolean,
default: false,
},
padLeft: {
type: Boolean,
default: false,
},
enforceBlackColour: {
type: Boolean,
default: false,
},
customStyled: {
type: Boolean,
default: false,
},
borderBottomWarning: {
type: Boolean,
default: false,
},
},
data(): {
selectedItem: any | null;
menuOpen: boolean;
searchText: string | null;
} {
return {
selectedItem: null,
menuOpen: false,
searchText: null,
};
},
mounted() {
const appElement = document.getElementById('app_home');
(appElement as any).addEventListener('click', this.handleDropdownClickOutside);
this.$nextTick(() => {
if (this.value) {
if (this.valueIsNumber) {
this.selectedItem = this.options.find((x: any) => x.value === Number(this.value)) || null;
} else {
this.selectedItem = this.options.find((x: any) => x.value.toString().toLowerCase() === this.value.toString().toLowerCase()) || null;
}
}
});
},
computed: {
v(): Validation | {} {
return this.validations;
},
errorMessage(): string {
// Validation must be cast to any to access validators
return Object.entries((this.v as Validation).$params).find(([k]) => !(this.v as any)[k]) ? .[1].message;
},
optgroups(): any {
return this.options.reduce((acc: any, o: any) => ({ ...acc,
[o.optgroup]: [...(acc[o.optgroup] || []), o]
}), {});
},
isRequired(): boolean {
return this.required !== false;
},
getSelectedItemText(): string | null {
return this.selectedItem ? this.selectedItem.text : this.placeholder || 'Please select an item';
},
filteredItems(): any[] {
const list: any[] = [];
for (let c = 0; c < 10; c += 1) {
list.push({
text: c,
value: c
});
}
// return this.searchText && this.searchText.length > 0 ? this.options.filter((x: any) => x.text.toLowerCase().indexOf(this.searchText!.toLowerCase()) > -1) : this.options;
return list;
},
},
methods: {
openMenu() {
this.menuOpen = !this.menuOpen;
if (this.menuOpen) {
this.searchText = null;
}
},
selectItem(item: any) {
this.selectedItem = item;
this.$emit('input', item.value);
this.menuOpen = false;
},
setSuppliedSelectedItem() {
this.$nextTick(() => {
if (this.value) {
this.selectedItem = this.options.find((x: any) => x.value === this.value) || null;
}
});
},
handleDropdownClickOutside(event: any): void {
const parent = document.getElementById(`select-${(this as any).uid}`);
const isParent = parent !== event.target && parent ? .contains(event.target);
if (!isParent) {
this.menuOpen = false;
// this.closeOpenendMenu();
// this.searchText = '';
}
},
},
beforeCreate() {
// eslint-disable-next-line no-plusplus
(this as any).uid = uidc++;
},
});
.dropdown {
font-size: 0.7rem;
img {
// float: right;
// padding-right: 10px;
// padding-top: 5px;
position: absolute;
top: 40%;
right: 10px;
}
.fade {
opacity: 0.5;
}
.search-box {
.form-control {
font-size: 12px !important;
height: 30px !important;
margin: 0 10px 5px 10px !important;
width: 95% !important;
}
}
.selected-item {
border-radius: 3px;
border: 1px solid #ced4da;
padding: 10px;
.selected-item-text {
text-overflow: ellipsis;
overflow: hidden;
width: 93%;
/* height: 1.2em; */
white-space: nowrap;
}
}
.items {
border: 1px solid rgb(236, 236, 236);
width: 100%;
z-index: 15;
max-height: 300px;
overflow-y: auto;
overflow-x: hidden;
background-color: white;
}
.item {
padding: 10px;
background-color: rgb(240, 240, 240);
cursor: pointer;
&:hover {
background-color: rgb(216, 216, 216);
}
}
}
.hidden {
opacity: 0.2;
}
.disabled {
background-color: #e9ecef;
opacity: 1;
pointer-events: none;
}
<template>
<div class="mt-2" :id="`select-${uid}`">
<label v-show="label" class="mb-2 label-grey" :class="{ 'required': isRequired }" :for="`select-${uid}`">{{ label }}</label>
<div class="dropdown noselect position-relative" :class="{'disabled': disabled}">
<div class="selected-item cursor-pointer" #click="openMenu">
<div class="selected-item-text" :class="{'fade': !selectedItem}">{{getSelectedItemText}}</div>
<img v-if="menuOpen" :src="constants.icons.arrowTop" />
<img v-else :src="constants.icons.arrowDown" />
</div>
<div class="items position-absolute" v-show="menuOpen">
<div v-if="filteredItems && filteredItems.length > 5 || searchText" class="search-box">
<input :size="'sm'" v-model="searchText" />
</div>
<div v-for="item in filteredItems" :key="item.value" #click="selectItem(item)">
<div class="item">
{{item.text}}
</div>
</div>
</div>
</div>
<span v-if="v.$error" class="text-error text-xs font-light">{{ errorMessage }}</span>
</div>
</template>
Suggest to use Floating-ui (well known as Poper)
Floating UI is a low-level library for positioning "floating" elements...intelligently keeping them in view
It's been using widely and cover a lot of edge cases you might encounter when try to create dropdown yourself
You can try with references here
creating-vue-component-dropdown-with-popper-js
floating-vue/dropdown
Iam using react Autosuggest component for city country list. When an input is given the suggested list of cities name with their respective countries is displayed. I am able to change the background color while hovering, but, I want to change when key pressed 'up' or 'down'. Following is the code for reference
Form.js
const getSuggestions = value => {
const inputValue = value.trim().toLowerCase();
const inputLength = inputValue.length;
return inputLength === 0 ? [] : cities.filter(data =>
data.name.toLowerCase().slice(0, inputLength) === inputValue
).slice(0,4);
};
// When suggestion is clicked, Autosuggest needs to populate the input
// based on the clicked suggestion.
const getSuggestionValue = suggestion => suggestion.name
const renderSuggestion = suggestion =>
(
<table className='auto-complete'>
<tbody>
<ArrowTooltip title={suggestion.name + ', ' + countries.getName(suggestion.country,"en")} placement="top">
<tr>
<td style={{ width: '88%' }} dangerouslySetInnerHTML=
{highlight(suggestion.name, suggestion.value)}></td>
<td style={{ width: '12%' }}>{suggestion.country}</td>
</tr>
</ArrowTooltip>
</tbody>
</table>
);
class Form extends React {
constructor() {
super();
this.state = {
value: '',
suggestions: []
};
}
onChange = (event, { newValue, method }) => {
this.setState({
value: newValue,
});
if(method == 'up' || method == 'down') {
// Here the value is triggered when key pressed
console.log('KEY UP/DOWN')
}
};
onSuggestionsFetchRequested = ({ value }) => {
this.setState({
suggestions: getSuggestions(value)
});
};
// Autosuggest will call this function every time you need to clear suggestions.
onSuggestionsClearRequested = () => {
this.setState({
suggestions: []
});
};
onSuggestionSelected = (event, {suggestion}) => {
const isSuggestion = true
this.props.getWeather(suggestion, isSuggestion)
}
render() {
const { value, suggestions} = this.state;
// Autosuggest will pass through all these props to the input.
const inputProps = {
placeholder: 'Search City...',
value,
onChange: this.onChange
};
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={this.onSuggestionsFetchRequested}
onSuggestionsClearRequested={this.onSuggestionsClearRequested}
getSuggestionValue={getSuggestionValue}
renderSuggestion={renderSuggestion}
inputProps={inputProps}
onSuggestionSelected={this.onSuggestionSelected}
/>
}
}
export default Form
CSS required for suggestions (which is to be styled)
Form.css
.react-autosuggest__suggestions-container {
display: none;
}
.react-autosuggest__suggestions-container--open {
display: block;
perspective: 1000px;
position:absolute;
top: 30px;
width: 180px;
font-family: 'Oxygen';
font-weight: 100;
font-size: 16px;
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
z-index: 2;
}
#media screen and (max-width: 576px) {
.react-autosuggest__suggestions-container--open, .react-autosuggest__input {
display: block;
width: 220px;
}
}
.react-autosuggest__suggestions-container--open li{
transform-origin: top center;
margin-bottom: 2px;
background: #c7ecee;
}
/* Here the background color is changed on hovering each li */
.react-autosuggest__suggestions-container--open li:hover {
background: #95afc0;
transition: 0.1s ease-in-out;
}
As seen above, when the mouse is hovered over each elements background color changes, but, I want to change when key (up or down) is pressed. Any suggestions or changes??
Just add theme prop. Something like this:
<Autosuggest
suggestions={suggestions}
onSuggestionsFetchRequested={
this.onSuggestionsFetchRequested
}
onSuggestionsClearRequested={
this.onSuggestionsClearRequested
}
getSuggestionValue={this.getSuggestionValue}
renderSuggestion={this.renderSuggestion}
inputProps={inputProps}
focusInputOnSuggestionClick={true}
theme={{
container: {
display: "block",
width: "100%",
position: "absolute",
borderTop: "none",
borderBottom: "1px solid #aaa",
borderLeft: "1px solid #aaa",
borderRight: "1px solid #aaa",
backgroundColor: "#fff",
fontFamily: "Helvetica, sans-serif",
fontWeight: 300,
fontSize: "16px",
borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px",
zIndex: 2,
paddingLeft: 0,
paddingRight: 0,
},
input: {
marginBottom: 0,
display: "block",
width: "100%",
height: "34px",
padding: "6px 12px",
fontSize: "14px",
lineHeight: "1.4285",
color: "#555",
backgroundColor: "#fff",
backgroundImage: "none",
borderRadius: "4px",
border: "1px solid #ccc",
},
suggestionHighlighted: {
backgroundColor: "lightgrey", //magic line
cursor:'pointer'
},
}}
/>
I generate play-off tournament bracket...
Usually in each tour there are 2^n participants: 16, 8, 4, 2. And it's easy to adjust them in flex column. But there is a tricky thing, when in first tour (round), participants count is between 16 and 8. And I need invisible duels, just like place holder in bracket, for adjustment.
I tried to do them {visibility: hidden}, they disappear on the screen. But unexpectedly partially appears when printing.
Why it happens?
How it looks on the screen (it's good, just like I planned):
And how it looks while printing (ctrl+P):
And there is a code:
import React from 'react'
import { Checkbox, Box } from '#material-ui/core'
import { makeStyles } from '#material-ui/core/styles'
import { find } from 'lodash'
import { athletName, trainerName } from '../../config/functions'
function DuelSimple(props) {
const {
duelData,
participants,
onFighterChange,
onWinnerChange,
showScoreInput,
showWinnerCheckbox
} = props
const { id, fighterRed, fighterBlue, winner, label /* scoreRed, scoreBlue */ } = duelData
const styles = {
duelTable: props => ({
/* width: '5cm', */
border: '1px solid gray',
borderRadius: 5,
marginBottom: 5,
borderSpacing: 0,
tableLayout: 'fixed',
visibility: props.duelData.status === 'fake' ? 'hidden' : 'visible'
}),
duelNumber: {
width: 30,
borderRight: '1px solid gray',
textAlign: 'center',
color: 'slategray'
},
duelLabel: {
fontSize: 9
},
athletRed: { width: 140, height: 33, padding: '0 5px', borderBottom: '1px solid gray' },
athletBlue: { width: 140, height: 33, padding: '0 5px' },
athletInput: { width: '100%', border: 'none' },
checkboxColumn: { width: 35, height: 33 },
checkboxColumnRed: { borderBottom: '1px solid gray' },
checkboxRed: { width: 7, height: 7, marginTop: 2, color: 'red' },
checkboxBlue: { width: 7, height: 7, marginTop: 2, color: 'blue' },
trainer: { color: 'gray', fontSize: 10 },
athletColorColumn: { width: 5 },
athletColorRed: { backgroundColor: 'rgba(255,0,0,0.5)', borderBottom: ' 1px solid gray' },
athletColorBlue: { backgroundColor: 'rgba(0,0,255,0.5)' },
scoreColumn: { width: 35, borderLeft: ' 1px solid gray' },
scoreColumnRed: {
'& input': { color: 'rgba(255,0,0,0.5)', fontWeight: 'bold' },
borderBottom: ' 1px solid gray'
},
scoreColumnBlue: { '& input': { color: 'rgba(0,0,255,0.5)', fontWeight: 'bold' } },
scoreInput: { border: 'none', width: 31, height: 25, textAlign: 'center' }
}
const useStyles = makeStyles(theme => styles)
const classes = useStyles(props)
let athletRedName, athletBlueName, trainerRedName, trainerBlueName
//populate fighters data by id
if (fighterRed) {
const participantRedPopulated = find(participants, { athlet: { id: fighterRed } })
athletRedName = athletName(participantRedPopulated.athlet)
trainerRedName = trainerName(participantRedPopulated.trainer)
} else {
athletRedName = ''
}
if (fighterBlue) {
const participantBluePopulated = find(participants, { athlet: { id: fighterBlue } })
athletBlueName = athletName(participantBluePopulated.athlet)
trainerBlueName = trainerName(participantBluePopulated.trainer)
} else {
athletBlueName = ''
}
return (
<table className={classes.duelTable}>
<tbody>
<tr>
<td className={classes.duelNumber} rowSpan='2'>
{id}
{label ? <div className={classes.duelLabel}>{label}</div> : ''}
</td>
<td className={classes.athletRed}>
<input
onChange={onFighterChange(id)}
type='text'
data-id={id}
data-color='Red'
className={classes.athletInput}
value={athletRedName}
/>
<div className={classes.trainer}>{trainerRedName}</div>
</td>
{showWinnerCheckbox && (
<td className={`${classes.checkboxColumnRed} ${classes.checkboxColumn}`}>
<Box displayPrint='none'>
<Checkbox
inputProps={{ 'data-winner': fighterRed }}
onChange={onWinnerChange(id)}
className={classes.checkboxRed}
checked={winner === fighterRed && winner ? true : false}
/>
</Box>
</td>
)}
{showScoreInput && (
<td className={`${classes.scoreColumn} ${classes.scoreColumnRed}`}>
<input className={classes.scoreInput} />
</td>
)}
<td className={`${classes.athletColorRed} ${classes.athletColorColumn}`}></td>
</tr>
<tr>
<td className={classes.athletBlue}>
<input
onChange={onFighterChange(id)}
type='text'
data-id={id}
data-color='Blue'
className={classes.athletInput}
value={athletBlueName}
/>
<div className={classes.trainer}>{trainerBlueName}</div>
</td>
{showWinnerCheckbox && (
<td className={`${classes.checkboxColumn}`}>
<Box displayPrint='none'>
<Checkbox
inputProps={{ 'data-winner': fighterBlue }}
onChange={onWinnerChange(id)}
className={classes.checkboxBlue}
checked={winner === fighterBlue && winner ? true : false}
/>
</Box>
</td>
)}
{showScoreInput && (
<td className={`${classes.scoreColumn} ${classes.scoreColumnBlue}`}>
<input className={classes.scoreInput} />
</td>
)}
<td className={`${classes.athletColorBlue} ${classes.athletColorColumn}`}></td>
</tr>
</tbody>
</table>
)
}
export default DuelSimple
And dependencies of the project:
{
"#material-ui/core": "^4.5.2",
"#material-ui/icons": "^4.5.1",
"firebase": "^7.2.3",
"react": "^16.11.0",
"react-dom": "^16.11.0",
"react-redux": "^7.1.1",
"react-redux-firebase": "^3.0.4",
"react-router-dom": "^5.1.2",
"react-scripts": "2.1.5",
"redux": "^4.0.4",
"redux-firestore": "^0.9.0"
}
I just set opacity:0, and it helped me )
Excuse me for troubling )
I am create custom Gutenberg editor block to set YouTube video Id to <button> atribute data-video=""
UPDATE - Added working code to the below
My function.php code
// Include Css and js block files
function designa_youtube_block() {
// Scripts
wp_register_script(
'youtube-block-script', // Handle.
'/wp-content/themes/twentyfifteen/init/block.js',
array( 'wp-blocks', 'wp-element', 'wp-i18n' ),
time()
);
// Styles
wp_register_style(
'youtube-block-editor-style', // Handle.
'/wp-content/themes/twentyfifteen/init/editor.css',
array( 'wp-edit-blocks' ),
time()
);
// Register the block with WP using our namespacing
register_block_type( 'designa/youtube-block', array(
'editor_script' => 'youtube-block-script',
'editor_style' => 'youtube-block-editor-style',
) );
}
add_action( 'init', 'designa_youtube_block' );
My block.js code
( function( editor, components, element ) {
const el = element.createElement;
const registerBlockType = wp.blocks.registerBlockType;
const RichText = wp.editor.RichText;
const BlockControls = wp.editor.BlockControls;
const InspectorControls = wp.editor.InspectorControls;
const AlignmentToolbar = wp.editor.AlignmentToolbar;
const UrlInput = wp.editor.URLInput;
function getVideoId(url) {
if (url) {
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|\&v=)([^#\&\?]*).*/);
if (match && match[2].length == 11)
return match[2];
else
return false;
} else {
return false;
}
}
registerBlockType( 'designa/youtube-block', {
title: 'YouTube',
description: 'Видео в всплывающем окне.',
icon: 'video-alt3',
category: 'common',
attributes: {
content: {
type: 'string',
},
url: {
type: 'string',
},
alignment: {
type: 'string',
},
},
edit: function( props, isSelected ) {
let attributes = props.attributes,
url = props.attributes.url,
alignment = props.attributes.alignment;
function onChangeAlignment( newAlignment ) {
props.setAttributes( { alignment: newAlignment } );
}
if (url)
videoTitle();
async function videoTitle(){
let id = getVideoId(url);
const response = await fetch( `https://www.googleapis.com/youtube/v3/videos?id=${ id }&key=AIzaSyBUqaVKkqdXzPQnfuuP8VPa-yqOQlJwV-w&fields=items(snippet(title))&part=snippet`, {
cache: 'no-cache',
headers: {
'user-agent': 'WP Block',
'content-type': 'application/json'
},
method: 'GET',
redirect: 'follow',
referrer: 'no-referrer',
})
.then(
returned => {
if (returned.ok) return returned;
throw new Error('Network response was not ok.');
}
);
let data = await response.json();
props.setAttributes( { title: data.items[0].snippet.title} );
};
return [
el( BlockControls, { key: 'controls' },
el( AlignmentToolbar, {
value: alignment,
onChange: onChangeAlignment,
} )
),
el( InspectorControls, { key: 'inspector' },
el( components.PanelBody, {
title: 'Превью видео',
className: 'youtube-preview-block',
initialOpen: true,
},
el( 'div', {
className: 'youtube-preview',
},
el( 'iframe', {
frameborder: '0',
allowfullscreen: 'allowfullscreen',
src: url ? 'https://www.youtube.com/embed/'+getVideoId(url)+'?rel=0&showinfo=0' : ''
},
),
),
el( 'div', { className: !props.attributes.title ? 'sceleton-youtube-title' : '' },
props.attributes.title ? props.attributes.title : ''
),
),
),
el( 'div', { className: alignment ? props.className+' justify-content-'+alignment : props.className },
el( 'svg', { className: 'play-icon', width: '50', height: '50', viewBox: '0 0 70 70' },
el( 'path', { fill: '#f6155e', d: "M35 70C54.33 70 70 54.33 70 35C70 15.67 54.33 0 35 0C15.67 0 0 15.67 0 35C0 54.33 15.67 70 35 70Z" } ),
el( 'path', { fill: '#fff', d: "M48.2988 35L28.6988 44.1L28.6988 25.9L48.2988 35Z" } ),
),
el( 'div', { className: 'youtube-wrap' },
el( RichText, {
tagName: 'div',
placeholder: 'Введите текст кнопки',
keepPlaceholderOnFocus: true,
isSelected: false,
value: props.attributes.content,
onChange: function( content ) {
props.setAttributes( {
content: content,
} );
}
}
),
el( 'div', { className: 'youtube-link-wrap' },
el( 'svg', { className: 'dashicon dashicons-admin-links', width: '20', height: '20' },
el( 'path', { d: "M17.74 2.76c1.68 1.69 1.68 4.41 0 6.1l-1.53 1.52c-1.12 1.12-2.7 1.47-4.14 1.09l2.62-2.61.76-.77.76-.76c.84-.84.84-2.2 0-3.04-.84-.85-2.2-.85-3.04 0l-.77.76-3.38 3.38c-.37-1.44-.02-3.02 1.1-4.14l1.52-1.53c1.69-1.68 4.42-1.68 6.1 0zM8.59 13.43l5.34-5.34c.42-.42.42-1.1 0-1.52-.44-.43-1.13-.39-1.53 0l-5.33 5.34c-.42.42-.42 1.1 0 1.52.44.43 1.13.39 1.52 0zm-.76 2.29l4.14-4.15c.38 1.44.03 3.02-1.09 4.14l-1.52 1.53c-1.69 1.68-4.41 1.68-6.1 0-1.68-1.68-1.68-4.42 0-6.1l1.53-1.52c1.12-1.12 2.7-1.47 4.14-1.1l-4.14 4.15c-.85.84-.85 2.2 0 3.05.84.84 2.2.84 3.04 0z" } ),
),
el( UrlInput, {
tagName: 'div',
value: url,
autoFocus: false,
onChange: function( url ) {
props.setAttributes({
url: url,
title: ''
});
}
}
),
),
),
),
];
},
save: function( props ) {
let attributes = props.attributes;
return (
el( 'div', {
className: (attributes.alignment) ? 'justify-content-'+attributes.alignment : '',
},
el( 'button', {
className: 'video-btn',
'data-video': getVideoId(attributes.url),
}, attributes.content,
),
)
);
},
} );
} )(
window.wp.editor,
window.wp.components,
window.wp.element,
);
My editor.css code
.youtube-wrap {
width: calc(80% - 70px);
}
.wp-block-designa-youtube-block {
display: flex;
align-items: center;
}
.youtube-link-wrap {
display: flex;
align-items: center;
}
.youtube-link-wrap svg {
margin-right: 10px;
fill: #8f98a1;
}
.youtube-wrap {
padding: 0 20px;
}
.youtube-preview {
position: relative;
padding-bottom: 56.25%;
height: 0;
background: #e1e9ee;
margin-bottom: 14px;
}
.gutenberg__editor .youtube-preview iframe {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
}
.justify-content-center {
justify-content: center;
}
.justify-content-left {
justify-content: flex-start;
}
.justify-content-right {
justify-content: flex-end;
}
.sceleton-youtube-title:before, .sceleton-youtube-title:after {
content: "";
display: block;
height: 18px;
width: 100%;
background-color: #f0f4f6;
margin-bottom: 10px;
}
.sceleton-youtube-title:after {
width: 40%;
}
.components-panel__body.youtube-preview-block {
font-weight: bold;
color: #5a5e61;
letter-spacing: 0.015em;
line-height: 22px;
}
In back end I got this result:
Everything works fine, but now I need to get the title and duration of the video from YouTube by ID getId(attributes.url) and insert to InspectorControls block.
If I try to get the title and duration using Ajax, first render all block fields to InspectorControls, and then execute my ajax request. I need something like that on ajax successful push( el( 'p', {}, '**YouTube Title**') )
Tell me how to add the information obtained with Ajax to InspectorControls after rendering and on change?
I have used auto-complete J-query as the following shows :
(function( $ ) {
$.widget( "custom.combobox", {
_create: function() {
this.wrapper = $( "<span>" )
.addClass( "custom-combobox" )
.insertAfter( this.element );
this.element.hide();
this._createAutocomplete();
this._createShowAllButton();
},
_createAutocomplete: function() {
var selected = this.element.children( ":selected" ),
value = selected.val() ? selected.text() : "";
this.input = $( "<input>" )
.appendTo( this.wrapper )
.val( value )
.attr( "title", "" )
.addClass( "custom-combobox-input ui-widget ui-widget-content ui-state-default ui-corner-left" )
.autocomplete({
delay: 0,
minLength: 0,
open: function() { $('ul.ui-autocomplete').width($( "input.custom-combobox-input" ).width() + 20 + "px") } ,
source: $.proxy( this, "_source" )
})
this._on( this.input, {
autocompleteselect: function( event, ui ) {
ui.item.option.selected = true;
this._trigger( "select", event, {
item: ui.item.option
});
},
autocompletechange: "_removeIfInvalid"
});
},
_createShowAllButton: function() {
var input = this.input,
wasOpen = false;
$( "<a>" )
.attr( "tabIndex", -1 )
.appendTo( this.wrapper )
.button({
icons: {
primary: "ui-icon-triangle-1-s"
},
text: false
})
.removeClass( "ui-corner-all" )
.addClass( "custom-combobox-toggle ui-corner-right" )
.mousedown(function() {
wasOpen = input.autocomplete( "widget" ).is( ":visible" );
})
.click(function() {
input.focus();
// Close if already visible
if ( wasOpen ) {
return;
}
// Pass empty string as value to search for, displaying all results
input.autocomplete( "search", "" );
});
},
_source: function( request, response ) {
var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
response( this.element.children( "option" ).map(function() {
var text = $( this ).text();
if ( this.value && ( !request.term || matcher.test(text) ) )
return {
label: text,
value: text,
option: this
};
}) );
},
_removeIfInvalid: function( event, ui ) {
// Selected an item, nothing to do
if ( ui.item ) {
return;
}
// Search for a match (case-insensitive)
var value = this.input.val(),
valueLowerCase = value.toLowerCase(),
valid = false;
this.element.children( "option" ).each(function() {
if ( $( this ).text().toLowerCase() === valueLowerCase ) {
this.selected = valid = true;
return false;
}
});
// Found a match, nothing to do
if ( valid ) {
return;
}
// Remove invalid value
this.input
.val( "" )
this.element.val( "" );
this.input.autocomplete( "instance" ).term = "";
},
_destroy: function() {
this.wrapper.remove();
this.element.show();
}
});
})( jQuery );
$(document).ready(function(){
$("#combobox").combobox();
$.ui.autocomplete.prototype._renderItem = function (ul, item) {
item.label = item.label.replace(new RegExp("(?![^&;]+;)(?!<[^<>]*)(" + $.ui.autocomplete.escapeRegex(this.term) + ")(?![^<>]*>)(?![^&;]+;)", "gi"), "<strong>$1</strong>");
return $("<li></li>")
.data("item.autocomplete", item)
.append("<a>" + item.label + "</a>")
.appendTo(ul);
};
});
.custom-combobox {
position: relative;
display: inline-block;
width:100%;
}
.custom-combobox-toggle {
position: absolute;
top: 0;
bottom: 0;
margin-left: -1px;
padding: 0;
width: 20px;
}
ul.ui-autocomplete{
max-height:200px;
overflow:auto;
}
.ui-autocomplete-term { font-weight: bold; color: blue; }
.ui-state-focus{
background:none !important;
background-color: blue !important;
border:none !important;
}
.custom-combobox-input {
background-color:green !important;
margin: 0;
background:initial;
}
<script src="http://code.jquery.com/jquery-1.9.1.js"></script>
<script src="http://code.jquery.com/ui/1.10.2/jquery-ui.js"></script>
<script type="text/javascript" src="jquery.ui.combobox.js"></script>
<link rel="stylesheet" href="http://code.jquery.com/ui/1.10.2/themes/smoothness/jquery-ui.css" />
<div style="background-color:red;width:70%">
<select id="combobox">
<option value="">Select one...</option>
<option value="ActionScript">ActionScript</option>
<option value="AppleScript">AppleScript</option>
</select>
<div/>
there is red div that have 70% width.
I want the input and the arrow button to have width 100% of their container [red div] like this
is there method to do that with using css only with out JavaScript?
THANKS :)
Use calc() but you need to account for borders etc so probably needs to be greater than 20px.
.custom-combobox-input {
background-color:green !important;
margin: 0;
background:initial;
width: calc(100% - 20px)
}