I would like to create an input field that accept multiple email addresses (for example to send an invitation). How can I achieve this without the help of jQuery or an external plugin or package?
I used to rely on bootstrap-tagsinput but I want to get rid of it, but I have no idea how to achieve the same thing without it.
This solution is quite a carbon copy of how the bootstrap tags input behaves but uses vanilla js only and some style rules.
It captures the click event on the container to create a text input inside that when will loose focus, will create a .tag span inside its parent with its original value (unless that value is empty spaces).
You may also change that condition so that it will create the tag only if the typed text matches a regular expression describing a valid email address.
const emailInput = document.getElementById('emailInputContainer');
//creates a tag element with the given text
function createTag(text){
const tag = document.createElement('span');
tag.classList.add('tag');
tag.innerText = text;
const remove = document.createElement('span');
remove.classList.add('remove');
tag.append(remove);
remove.addEventListener('click', (event)=>{
event.currentTarget.parentElement.remove();
});
return tag;
}
//creates and returns an input element
function createNewInput(){
const newInput = document.createElement('input');
newInput.classList.add('tempinput');
newInput.addEventListener('focusout', (event)=>{
const target = event.currentTarget;
if(target.value.trim().length > 0){
const tag = createTag(target.value);
target.parentElement.append(tag);
}
target.remove();
});
return newInput;
}
//adds the click event listener to the input container
emailInput.addEventListener('click', (event)=>{
const target = event.currentTarget;
const newInput = createNewInput();
target.append(newInput);
newInput.focus();
});
body{
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
}
#emailInputContainer{
border: solid 1px;
background-color: #fff;
border: 1px solid #ccc;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
display: inline-block;
padding: 4px 6px;
color: #555;
vertical-align: middle;
border-radius: 4px;
line-height: 22px;
cursor: text;
width: 100%;
height: 1.5em;
}
.tag{
padding: 2px 5px;
margin-right: 5px;
background: #5bc0de;
color: white;
display: inline;
padding: .2em .6em .3em;
font-size: 75%;
font-weight: 700;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: .25em;
}
.tempinput{
border: none;
outline: none;
}
.tag > .remove{
margin-left: 8px;
cursor: pointer;
}
.tag > .remove::after{
content: "x";
padding: 0px 0px;
}
<div id="emailInputContainer" tabindex="0">
</div>
I have the below LESS stylesheet and I know there has to be a better way to organize this. Is the only option to create a map containing the classes and a mixin perhaps to repeat the styles?
// child div is injected by JS
.ddemrcontent > span, .blocksmarttemplate > span, .blocktoken > span {
display: inline-flex;
align-items: center;
min-height: 1.7rem;
margin-top: 0.2rem;
padding-left: 0.2rem;
}
.ddfreetext {
display: flex;
min-height: 1.7rem;
margin-top: 0.2rem;
}
.ddemrcontent > span:hover, .blocksmarttemplate > span:hover, .blocktoken > span:hover {
text-decoration: underline;
cursor: pointer;
}
.ddemrcontent > span {
border-left: 4px solid cadetblue;
}
.blocksmarttemplate > span {
border-left: 4px solid burlywood;
}
.blocktoken > span {
border-left: 4px solid #8a7090;
}
.ddfreetext {
border: 1px dashed black;
}
UPDATE
Here is the best I've been able to come up with. Since the & parent selector won't apply to each distinct parent selector (that are comma delimited) I think I am forced to use a mixin and call it to apply the rules for each parent I have.
Would love to hear if there's still a better way.
.dyndoccontent(#color) {
& > span {
display: inline-flex;
align-items: center;
min-height: 1.7rem;
margin-top: 0.2rem;
padding-left: 0.2rem;
border-left: 4px solid #color;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
// child div is injected by JS
.ddemrcontent {
.dyndoccontent(cadetblue);
}
.blocksmarttemplate {
.dyndoccontent(burlywood);
}
.blocktoken {
.dyndoccontent(#8a7090);
}
.ddfreetext {
display: flex;
min-height: 1.7rem;
margin-top: 0.2rem;
border: 1px dashed black;
}
I would definitely recommend mixin if you have multiple parts in your less files which use the same styles.
For you example i would go for a more nested way:
// child div is injected by JS
.ddemrcontent, .blocksmarttemplate, .blocktoken {
& > span {
display: inline-flex;
align-items: center;
min-height: 1.7rem;
margin-top: 0.2rem;
padding-left: 0.2rem;
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
}
.ddfreetext {
border: 1px dashed black;
display: flex;
min-height: 1.7rem;
margin-top: 0.2rem;
}
.ddemrcontent > span {
border-left: 4px solid cadetblue;
}
.blocksmarttemplate > span {
border-left: 4px solid burlywood;
}
.blocktoken > span {
border-left: 4px solid #8a7090;
}
I am quite new to vue.js, using vue 2 and nuxt.js. I am trying to build an authentication in a website. There is a backend api running in local.
I have a form that receives my user's data. The data is then stored in the store using vuex. I want to make a POST Api call using axios, which retrieves the data stored from the store to the API running localy.
Unfortunately, I cannot get axios to run this properly...
It seems that the sendData function cant have access to the store. I tried many things : this.$store.user, this.user, $store...
Thanks a lot for your help!
4th Component
<template>
<form class="form-horizontal" >
<!-- firstname input-->
<div class="field">
<label class="label" for="contact_first_name">Prénom</label>
<div class="control">
<input id="contact_first_name" name="contact_first_name" type="contact_first_name" placeholder="Prénom" class="input " required="" v-model="companycontact.contact_first_name">
</div>
</div>
<!-- name input-->
<div class="field">
<label class="label" for="contact_last_name">Nom</label>
<div class="control">
<input id="contact_last_name" name="contact_last_name" type="contact_last_name" placeholder="Nom" class="input " required="" v-model="companycontact.contact_last_name">
</div>
</div>
<!-- service choice -->
<div class="field">
<label class="label" for="Service">Service</label>
<div class="control">
<div class="formfield-select">
<select id="service-choice" name="service-choice" class="input" v-model="companycontact.contact_service">
<option v-for="(service, index) in services" :key="index" :value="service.value">
{{ service.text }}
</option>
</select>
</div>
</div>
<div class="next-back-button">
<nuxt-link to="/SignUpCompanyOnline" class="button is-rounded back-button" #click.native="saveStore()">Retour</nuxt-link>
<nuxt-link to="/SignUpCompanyIg" class="button is-rounded next-button" #click.native="saveStore(); sendData()">Suivant</nuxt-link>
</div>
</div>
</form>
</template>
<script>
export default {
name: 'SignUpCompanyContact',
data () {
return {
companycontact: {
contact_first_name: this.contact_first_name,
contact_last_name: this.contact_last_name,
services: this.services,
},
services: [
{ text: 'Communication', value: 'COM' },
{ text: 'Direction', value: 'DIR' },
{ text: 'Marketing', value: 'MAR' },
{ text: 'Autre', value: 'OTH' },
],
}
},
methods: {
saveStore() {
this.$store.commit('signUp/setContactServiceInsta', { companycontact: this.companycontact })
},
async sendData () {
// console.log(this.user.email)
// console.log(this.$store.user.email)
// console.log(user.email)
// console.log($store)
/* let data = {
email: this.user.email,
password: this.password,
nb_employees: this.numberemployee,
name: this.name,
insta_account: this.insta_account,
sale_type: this.option,
sector_type: this.sector,
website_url: this.website_url,
contact_first_name: this.contact_first_name,
contact_last_name: this.contact_last_name,
contact_service: this.contact_service
}
// console.log(data);
this.$axios.post('http://localhost:8000/api/companies/', data )
.then(response => console.log(response))
.catch(error => console.log(error.response)) */
}
}
}
</script>
<style>
.form-example {
padding-bottom: 20px;
}
.form-logo{
width: 30px;
height: 30px;
border-bottom:2px solid #D8D8D8;
}
input {
width: 300px;
}
input[placeholder] {
width: 300px;
}
/* contact firstname lastname */
input[type=contact_last_name] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: none;
border-bottom: 2px solid #D8D8D8;
background-image: url(../../assets/icons/user-lightgrey.svg);
background-size: 30px;
background-position-y: 4px;
background-repeat: no-repeat;
text-indent: 20px;
}
input[type=contact_last_name]:focus {
border-bottom: 2px solid #7F7F7F;
outline: none;
background-image: url(../../assets/icons/user-darkgrey.svg);
-webkit-box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
-moz-box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
transition-duration: .3s;
border-radius: 1px;
}
input[type=contact_first_name] {
width: 100%;
padding: 12px 20px;
margin: 8px 0;
box-sizing: border-box;
border: none;
border-bottom: 2px solid #D8D8D8;
background-image: url(../../assets/icons/user-lightgrey.svg);
background-size: 30px;
background-position-y: 4px;
background-repeat: no-repeat;
text-indent: 20px;
}
input[type=contact_first_name]:focus {
border-bottom: 2px solid #7F7F7F;
outline: none;
background-image: url(../../assets/icons/user-darkgrey.svg);
-webkit-box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
-moz-box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
box-shadow: 0px 14px 8px -8px rgba(0,0,0,0.40);
transition-duration: .3s;
border-radius: 1px;
}
/* service */
#service-choice {
padding-left: 20px !important;
margin: 8px 0;
box-sizing: border-box;
color: #4A4A4A !important;
border: 1px solid #fff !important;
border-bottom: 2px solid #D8D8D8 !important;
background-image: url(../../assets/icons/sector-lightgrey.svg);
background-size: 30px;
background-repeat: no-repeat;
text-indent: 20px;
outline: none !important;
}
#service-choice:focus {
border-bottom: 2px solid #7F7F7F;
outline: none;
color: #4A4A4A !important;
background-image: url(../../assets/icons/sector-darkgrey.svg);
border-radius: 1px;
}
/* submit button */
.submit-button {
text-decoration: none;
outline: none;
background-color: #00CC99;
border: 1px solid #00CC99;
font-size: 20px;
color: #fff;
font-weight: normal;
padding: 10px 20px;
margin-top: 40px;
font-family: 'Montserrat', sans-serif;
-webkit-box-shadow: 0px 0px 44px -9px rgba(0,0,0,0.40);
-moz-box-shadow: 0px 0px 44px -9px rgba(0,0,0,0.40);
box-shadow: 0px 0px 44px -9px rgba(0,0,0,0.40);
}
.submit-button:hover {
background-color: #fff;
border: 1px solid #00CC99;
color: #00CC99;
transition: background-color .3s;
}
/* next button / back button */
.next-back-button {
display: flex;
justify-content: space-evenly;
flex-direction: row;
width: 50%;
}
.next-button {
text-decoration: none;
outline: none;
background-color: #00CC99;
border: 1px solid #00CC99;
font-size: 20px;
color: #fff;
font-weight: normal;
padding: 10px 20px;
margin-top: 40px;
font-family: 'Montserrat', sans-serif;
}
.next-button:hover {
background-color: #fff;
border: 1px solid #00CC99;
color: #00CC99;
transition: background-color .3s;
}
.back-button {
text-decoration: none;
outline: none;
background-color: #ffffff;
border: 1px solid #D8D8D8;
font-size: 20px;
color: #D8D8D8;
font-weight: normal;
padding: 10px 20px;
margin-top: 40px;
font-family: 'Montserrat', sans-serif;
outline: none;
}
.back-button:hover {
background-color: #fff;
border: 1px solid #999999;
color: #999999;
transition: background-color .3s;
}
</style>
VUEX store :
export const state = () => ({
user: {},
company: {},
companydetails: {},
companycontact: {},
validationcode: {},
})
export const mutations = {
setEmailPassword(state, { user }) {
console.log(user)
state.user.email = user.email
state.user.password = user.password
},
setCompanyEmployeeInsta(state, { company }) {
console.log(company)
state.company.name = company.name
state.company.insta_account = company.insta_account
state.company.selected = company.selected
},
setWebsiteCanalSector(state, { companydetails }) {
console.log(companydetails)
state.companydetails.website_url = companydetails.website_url
state.companydetails.option = companydetails.option
state.companydetails.sector = companydetails.sector
},
setContactServiceInsta(state, { companycontact }) {
console.log(companycontact)
state.companycontact.contact_first_name = companycontact.contact_first_name
state.companycontact.contact_last_name = companycontact.contact_last_name
state.companycontact.contact_service = companycontact.contact_service
},
setValidationCode(state, { validationcode }) {
console.log(validationcode)
state.validationcode.contact_first_name = validationcode.contact_first_name
},
}
I am using the npm package https://www.npmjs.com/package/vuejs-paginate
to handle pagination in a vuejs application.
I would like to style this pagination component.
My styling successfully sets the background of page number buttons yellow when the user hovers over them, but fails to set the background of the current page to green. Why?
Here is my component tag with the props.
<paginate
:pageCount="totalPages"
:click-handler="paginateCallback"
:prevText="'Prev'"
:nextText="'Next'"
:containerClass="'pagination'"
class="pagination"
v-model="pageNumber"
></paginate>
And here is the css...
.pagination a {
float: left;
padding: 8px 16px;
text-decoration: none;
border: 1px solid #ddd;
background-color: white;
}
.pagination a.active {
background-color: green;
}
.pagination a:hover:not(.active) {background-color: yellow;}
.pagination a:first-child {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
.pagination a:last-child {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
By the way, in case it is relevant information, the application uses bootstrap-vue elsewhere.
Thanks to the first answer below, I was able to resolve this.
Here is the working css after adding the active-class prop to the component...
.pagination li {
float: left;
padding: 8px 16px;
text-decoration: none;
border: 1px solid #ddd;
color: white;
background-color: white;
font-size: 1em;
}
.pagination li.pagination-active {
background-color: green;
}
.pagination li:hover:not(.active) {background-color: yellow;}
Now, however, there is a border around the number of the active page button until the user clicks again anywhere on the page. How can we eliminate this border?
As the documentations says: there is an active class prop that you can set and style that class. see the props in the link above.
<paginate
:pageCount="totalPages"
:click-handler="paginateCallback"
:prevText="'Prev'"
:nextText="'Next'"
:active-class="myActiveBtn"
:containerClass="'pagination'"
class="pagination"
v-model="pageNumber"
></paginate>
style:
.myActiveBtn{
background-color: green;
}
I am avoiding including the entire bootstrap source code in my app. All I need at this point is the bootstrap dropdown classes. I am trying to make a simple dropdown that looks like this...
<div class="dropdown">
<a data-toggle="dropdown" class="dropdown-toggle">display dropdown</a>
<ul
class="dropdown-menu dropdown-menu-right dropdown-menu-arrow dropdown-scrollable dropdown-content"
>
<li class="dropdown-item">Option 1</li>
<li class="dropdown-item">Option 2</li>
</ul>
</div>
I copied dropdown.scss from the tabler.io library.
This is all it contains...
.dropdown {
display: inline-block;
color: orange;
}
.dropdown-menu {
box-shadow: $dropdown-box-shadow;
min-width: 12rem;
}
.dropdown-item {
color: $text-muted-dark;
z-index: 1000;
}
.dropdown-menu-arrow {
&:before {
position: absolute;
top: -6px;
left: 12px;
display: inline-block;
border-right: 5px solid transparent;
border-bottom: 5px solid $border-color;
border-left: 5px solid transparent;
border-bottom-color: rgba(0, 0, 0, 0.2);
content: "";
}
&:after {
position: absolute;
top: -5px;
left: 12px;
display: inline-block;
border-right: 5px solid transparent;
border-bottom: 5px solid #fff;
border-left: 5px solid transparent;
content: "";
}
&.dropdown-menu-right {
&:before,
&:after {
left: auto;
right: 12px;
}
}
}
.dropdown-toggle {
user-select: none;
cursor: pointer;
&:after {
vertical-align: 0.155em;
}
&:empty:after {
margin-left: 0;
}
}
.dropdown-icon {
color: $text-muted;
margin-right: 0.5rem;
margin-left: -0.5rem;
width: 1em;
display: inline-block;
text-align: center;
vertical-align: -1px;
}
I know that my code is referencing this stylesheet because the font color is orange and the stylesheet's first rule includes color: orange.
I also know that my code detects the user clicking on the text because I tested by adding #click.prevent="doSomething()" where doSomething() simply console logged a message.The message did indeed print out in the Chrome dev tools console.
However, when I click on the words display dropdown, the dropdown menu does not open.
I understand there are javascript files I may need. Which files are those and how can I make sure my code uses this file? This is a Vuejs app using Nuxt.