Is there a way of triggering an event for the date range picker of v-calendar after the first date is picked or stopping the inputs from adding the dates until both dates have been selected?
I have the following vue component:
new Vue({
el: "#app",
data() {
return {
range: {
start: null,
end: null
}
};
},
methods: {
handleBlur(event) {
if (event.currentTarget.value === '') {
event.currentTarget.parentNode.classList.remove("entered");
}
},
handleFocus(event) {
event.currentTarget.parentNode.classList.add("entered");
},
moveLabels() {
changeClass(this.$refs.filterDateForm);
changeClass(this.$refs.filterDateTo);
}
}
});
function changeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
} else {
input.parentNode.classList.add("entered");
}
}
#import url 'https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.min.css';
.filter__date-range-holder {
display: flex;
justify-content: space-between;
width: 95%;
}
.filter__date-range-column {
width: calc(50% - 15px);
}
.form__row {
position: relative;
margin: 1.5em 0;
background: white;
}
.form__control {
width: 100%;
border: 1px solid grey;
font-size: 1rem;
line-height: 1.5rem;
color: black;
padding: 0.75em;
background: transparent;
}
.invalid .form__control {
border-color: red;
outline-color: red;
}
.form__control:focus {
border-radius: 0;
}
.form__label {
display: inline-block;
position: absolute;
top: 50%;
left: calc(0.75em + 1px);
transform: translateY(-50%);
z-index: 1;
color: black;
background: white;
transition: all 0.25s ease-in-out;
pointer-events: none;
}
.entered .form__label {
top: 0;
left: 0.5rem;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 0.2em;
}
.invalid .form__label {
color: red;
}
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.umd.min.js"></script>
<div id='app'>
<v-date-picker v-model="range" :popover="{ visibility: 'focus' }" is-range #input="moveLabels">
<template #default="{ inputValue, inputEvents }">
<div class="filter__date-range-holder">
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-from">From</label>
<input id="filter-date-from" ref="filterDateForm" type="text" name="from" class="form__control form__control--textbox" :value="inputValue.start" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-to">To</label>
<input id="filter-date-to" ref="filterDateTo" type="text" name="to" class="form__control form__control--textbox" :value="inputValue.end" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
</div>
</template>
</v-date-picker>
</div>
As you can see, the label starts off inside the textbox and animates to the top on focus or if there is a value in the input. However, with the date range picker, as soon as you select the first date, it updates both inputs with the selected date and so my label is over the value.
I have tried setting the #input event of the date picker and putting a watch on the range variable, but both only fire once both dates have been selected so I can only move my labels after the second date is selected.
I have also tried adding an #change event to the inputs, but as the value is only change via js, the change event is not picked up
In the end I solved this by doing the following:
Add an #input event to handle when date range is selected properly
Add a #dayclick event to add the entered class when a day is selected
Add a timeout to the handleBlur method (the #dayClick event seemed to take a bit of time to fire so the blur animation started before it kicked in)
Add a mutation observer to see if the calendar closes - as the calendar doesn't have a close event, I needed to see if the calendar was closed without valid date range selected - if this happened and the inputs were emptied, this observer removed the entered class
new Vue({
el: "#app",
data() {
return {
range: {
start: null,
end: null
}
};
},
mounted() {
const overlay = document.querySelector('.filter__overlay');
const config = {
attributes: false,
childList: true,
subtree: true
};
// watch to see if calendar is closed
const observer = new MutationObserver(mutationsList => {
mutationsList.forEach(mutation => {
if (mutation.type === 'childList' &&
mutation.removedNodes.length > 0 &&
mutation.removedNodes[0].classList &&
mutation.removedNodes[0].classList.contains('vc-popover-content')) {
removeClass(this.$refs.filterDateForm);
removeClass(this.$refs.filterDateTo);
}
});
});
observer.observe(overlay, config);
},
methods: {
handleBlur(event) {
const input = event.currentTarget;
setTimeout(() => {
removeClass(input);
}, 150);
},
handleFocus(event) {
event.currentTarget.parentNode.classList.add("entered");
},
handleCalendarBlur() {
changeClass(this.$refs.filterDateForm);
changeClass(this.$refs.filterDateTo);
},
handleCalendarClick() {
this.$refs.filterDateForm.parentNode.classList.add("entered");
this.$refs.filterDateTo.parentNode.classList.add("entered");
},
}
});
function removeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
}
}
function changeClass(input) {
if (input.value === '') {
input.parentNode.classList.remove("entered");
} else {
input.parentNode.classList.add("entered");
}
}
#import url 'https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.min.css';
.filter__date-range-holder {
display: flex;
justify-content: space-between;
width: 95%;
}
.filter__date-range-column {
width: calc(50% - 15px);
}
.form__row {
position: relative;
margin: 1.5em 0;
background: white;
}
.form__control {
width: 100%;
border: 1px solid grey;
font-size: 1rem;
line-height: 1.5rem;
color: black;
padding: 0.75em;
background: transparent;
}
.invalid .form__control {
border-color: red;
outline-color: red;
}
.form__control:focus {
border-radius: 0;
}
.form__label {
display: inline-block;
position: absolute;
top: 50%;
left: calc(0.75em + 1px);
transform: translateY(-50%);
z-index: 1;
color: black;
background: white;
transition: all 0.25s ease-in-out;
pointer-events: none;
}
.entered .form__label {
top: 0;
left: 0.5rem;
font-size: 0.6875rem;
line-height: 0.6875rem;
padding: 0.2em;
}
.invalid .form__label {
color: red;
}
<script src="https://unpkg.com/vue#2.6.14/dist/vue.js"></script>
<script src="https://unpkg.com/v-calendar#2.3.4/lib/v-calendar.umd.min.js"></script>
<div id='app'>
<div class="filter__overlay">
<v-date-picker v-model="range" :popover="{ visibility: 'focus' }" is-range #input="handleCalendarBlur" #dayclick="handleCalendarClick">
<template #default="{ inputValue, inputEvents }">
<div class="filter__date-range-holder">
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-from">From</label>
<input id="filter-date-from" ref="filterDateForm" type="text" name="from" class="form__control form__control--textbox" :value="inputValue.start" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
<div class="filter__date-range-column">
<div class="form__row filter__date-range-row">
<label class="form__label filter__date-range-label" for="filter-date-to">To</label>
<input id="filter-date-to" ref="filterDateTo" type="text" name="to" class="form__control form__control--textbox" :value="inputValue.end" v-on="inputEvents.start" #focus="handleFocus" #blur="handleBlur">
</div>
</div>
</div>
</template>
</v-date-picker>
</div>
</div>
Please note this example works differently to the one in my site - for some reason the one in my site will remove the dates from the inputs if only one date is selected - this one seems to keep it
Related
I've implemented a function to change the background color of my checkbox , but it makes the tickmark turns to color black which I don't want , I want the mark to still white , how can I achieve this ?
HTML :-
<div v-for="category in categories" :key="category.id">
<div>
<input type="checkbox" class="categoryInput" #change="input()"
:true-value="category.id" false-value="0" v-model="currentCategory"/>
<label class="form-label">{{category.name}}</label>
</div>
</div>
here's the function :-
input(){
var color = JSON.parse(localStorage.getItem('coloring') || '[]').CTAButtons
let collection = document.getElementsByClassName("categoryInput");
for (let i = 0; i < collection.length; i++) {
collection[i].style.accentColor = color
}
}
and here's the output :-
the background changed successfully but the tickmark changed to color black
The tickmark color for the default HTML checkbox is decided by the browser and cannot be changed. You can however create your own custom checkbox and style it however you want.
HTML
<label class="container">
<input type="checkbox" checked="checked" />
<span class="checkmark"></span>
</label>
CSS
.container {
display: block;
position: relative;
padding-left: 35px;
margin-bottom: 12px;
cursor: pointer;
font-size: 22px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
/* Hide the browser's default 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: 25px;
width: 25px;
background-color: #eee;
}
/* On mouse-over, add a grey background color */
.container:hover input ~ .checkmark {
background-color: #ccc;
}
/* When the checkbox is checked, add a teal background */
.container input:checked ~ .checkmark {
background-color: #3bb0a8;
}
/* Create the checkmark/indicator (hidden when not checked) */
.checkmark:after {
content: '';
position: absolute;
display: none;
}
/* Show the checkmark when checked */
.container input:checked ~ .checkmark:after {
display: block;
}
/* Style the checkmark/indicator */
.container .checkmark:after {
left: 9px;
top: 5px;
width: 5px;
height: 10px;
border: solid white;
border-width: 0 3px 3px 0;
-webkit-transform: rotate(45deg);
-ms-transform: rotate(45deg);
transform: rotate(45deg);
}
JSFiddle
The advantage of going with vue is to build custom components. So you can try something as shown below.
Working StackBlitz
You might want to tweak your code depending on your exact requirement.
<template>
<div v-for="category in categories" :key="category.id">
<div>
<label class="form-label categoryInput">
<span class="material-icons icon" v-if="isChecked(category.id)"
>check_box</span
>
<span
class="material-icons-outlined icon"
v-if="!isChecked(category.id)"
>check_box_outline_blank</span
>
<input
type="checkbox"
class=""
#change="input()"
:value="category.id"
v-model="currentCategory"
/>
{{ category.name }}</label
>
</div>
</div>
</template>
<script>
export default {
name: 'Checkbox',
data: function () {
return {
currentCategory: [],
categories: [
{ id: 1, name: 'alpha' },
{ id: 2, name: 'beta' },
],
};
},
props: {},
methods: {
isChecked(categoryId) {
return this.currentCategory.indexOf(categoryId) !== -1;
},
input() {
var color = '#3bb0a8';
let collection = document.getElementsByClassName('categoryInput');
for (let i = 0; i < collection.length; i++) {
const icons = collection[i].querySelectorAll('.icon');
icons.forEach((iconEle) => {
iconEle.style.color = color;
});
}
},
},
};
</script>
<style scoped>
label.categoryInput {
display: flex;
justify-content: center;
align-items: center;
}
label.categoryInput input[type='checkbox'] {
width: 0;
height: 0;
}
</style>
Important
Add below to your index.html header
<link href="https://fonts.googleapis.com/css?family=Material+Icons|Material+Icons+Outlined|Material+Icons+Two+Tone|Material+Icons+Round|Material+Icons+Sharp" rel="stylesheet">
Any time I have elements within my div ".packagebuilder" get class ".shown" added it adds code to fluidly transition. It works when the pricing table appears but not when all the other elements come into view. When ".hidden" gets added it should fluidly go back into place, but it doesn't transition at all, just immediately flashes back. What am I missing? Appreciate any help.
.hidden {
opacity: 0;
max-height: 0px;
transition: max-height 0s cubic-bezier(0, 1, 0, 1) 0s;
position:absolute;
top:-100000px;
}
.shown {
opacity: 1;
max-height: 1000px;
transition: max-height 1s ease-in-out 0s;
position:relative;
}
This is the code. Anything with the class "hidden" will get "shown" added onclick of an input.
var seoSelect = document.querySelector("select#seo");
var designSelect = document.querySelector("select#design");
var anySelect = document.querySelector(".packagebuilder select");
var seoOption = document.querySelector(".seo.option");
var designOption = document.querySelector(".design.option");
var anyOption = document.querySelector(".packagebuilder .option");
var estimateTable = document.querySelector(".estimate");
//Price Table Items
var seofirstmonthitem = document.querySelector(".item.seofirstmonth");
var seosetupfeeitem = document.querySelector(".item.setupfee");
var designdeposititem = document.querySelector(".item.designdeposit");
var designpayoffitem = document.querySelector(".item.designpayoff");
var seomonthlyitem = document.querySelector(".item.seomonthly");
var dueToday = document.querySelector(".today #val");
var dueLater = document.querySelector(".later #val");
var dueMonthly = document.querySelector(".monthly #val");
function hidePriceTable() {
$(estimateTable).addClass("hidden");
$(estimateTable).removeClass("shown");
$(".disclaimer.pricing").addClass("hidden");
$(".disclaimer.pricing").removeClass("shown");
$(".button.package").addClass("hidden");
$(".button.package").removeClass("shown");
}
function showPriceTable() {
$(estimateTable).removeClass("hidden");
$(estimateTable).addClass("shown");
$(".disclaimer.pricing").removeClass("hidden");
$(".disclaimer.pricing").addClass("shown");
$(".button.package").removeClass("hidden");
$(".button.package").addClass("shown");
}
function HideSE0() {
$(seoOption).addClass("hidden");
$(seoOption).removeClass("shown");
}
function ShowSEO() {
$(seoOption).addClass("shown");
$(seoOption).removeClass("hidden");
}
function HideDesign() {
$(designOption).addClass("hidden");
$(designOption).removeClass("shown");
}
function ShowDesign() {
$(designOption).addClass("shown");
$(designOption).removeClass("hidden");
}
function hideSEOcontent() {
$(".item.seofirstmonth").hide();
$(".item.seomonthly").hide();
$(".item.setupfee").hide();
$('select#seo').prop('selectedIndex', null);
$("input#seosetup").val("0");
$("input#seovalue").val("0");
HideSE0();
}
function showSEOcontent() {
$(".item.seofirstmonth").show();
$(".item.seomonthly").show();
$(".item.setupfee").show();
ShowSEO();
}
function hideDesigncontent() {
$(".item.designdeposit").hide();
$(".item.designpayoff").hide();
$('select#design').prop('selectedIndex', null);
$("input#designdeposit").val("0");
$("input#designpayoff").val("0");
$("input#designtotal").val("0");
HideDesign();
}
function showDesigncontent() {
$(".item.designdeposit").show();
$(".item.designpayoff").show();
ShowDesign();
}
function hideAll() {
hideSEOcontent();
hidePriceTable();
HideDesign();
HideSE0();
}
hideAll();
$(function () {
setInterval(constantChecker, 1);
});
/// Web Design Package Choices + Pricing /////////////////////////////////////////////////
function designSelectValues() {
// Set Default values
$('select#design').prop('selectedIndex', null);
//Check for new package choice
$(designSelect).change(function () {
if (designSelect.value == "1") {
$("input#designdeposit").val("1500");
$("input#designpayoff").val("1500");
$("input#designtotal").val("3000");
} else if (designSelect.value == "2") {
$("input#designdeposit").val("2500");
$("input#designpayoff").val("2500");
$("input#designtotal").val("5000");
} else if (designSelect.value == "3" || designSelect.value == "0" ) {
$("input#designdeposit").val("0");
$("input#designpayoff").val("0");
$("input#designtotal").val("0");
} else {
$("input#designdeposit").val("1500");
$("input#designpayoff").val("1500");
$("input#designtotal").val("3000");
}
});
}
/// SEO Package Choices + Pricing /////////////////////////////////////////////////
function seoSelectValues() {
// Set Default values
$('select#seo').prop('selectedIndex', null);
//Check for new package choice
$(seoSelect).change(function () {
if (seoSelect.value == "1") {
$("input#seosetup").val("500");
$("input#seovalue").val("750");
} else if (seoSelect.value == "2") {
$("input#seosetup").val("1000");
$("input#seovalue").val("1500");
} else if (seoSelect.value == "3") {
$("input#seosetup").val("1500");
$("input#seovalue").val("2500");
} else if (seoSelect.value == "4" || seoSelect.value == "0" ) {
$("input#seosetup").val("0");
$("input#seovalue").val("0");
} else {
$("input#seosetup").val("500");
$("input#seovalue").val("750");
}
});
}
// When service added, activate options and values ////////////////////////////////////
$('#chooseseo').change(function () {
if (!$('#chooseseo').is(':checked')) {
hideSEOcontent();
$("input#seosetup").val("0");
$("input#seovalue").val("0");
} else if ($('#chooseseo').is(':checked')) {
showSEOcontent();
seoSelectValues();
}
});
$('#choosedesign').change(function () {
if (!$('#choosedesign').is(':checked')) {
hideDesigncontent();
$("input#designdeposit").val("0");
$("input#designpayoff").val("0");
$("input#designtotal").val("0");
} else if ($('#choosedesign').is(':checked')) {
showDesigncontent();
designSelectValues();
}
});
// Convert String to US Currency ///////////////////////////////////////////////////////////
var formatter = new Intl.NumberFormat('en-US', {
style: 'currency',
currency: 'USD',
});
// Constanly check for new numbers /////////////////////////////////////////////////////////////
function constantChecker() {
// Show or Hide Custom Disclaimer ///
// if ($(seoSelect).val() == "4" || $(designSelect).val() == "3") {
// $(".disclaimer.custom").show();
// } else if ($(seoSelect).val() != "4" || $(designSelect).val() != "3") {
// $(".disclaimer.custom").hide();
// }
if (($("select#design")[0].selectedIndex === 0) && ($("select#seo")[0].selectedIndex === 0)) {
hidePriceTable();
}
else {
showPriceTable();
}
// Get Current Select Values /////
var currentSeoSetup = $("input#seosetup").val();
var currentSeoValue = $("input#seovalue").val();
var currentDesignDeposit = $("input#designdeposit").val();
var currentDesignPayoff = $("input#designpayoff").val();
var currentDueToday = formatter.format(+currentDesignDeposit + +currentSeoSetup + +currentSeoValue);
var currentDueLater = formatter.format(+currentDesignPayoff);
var currentMonthly = formatter.format(+currentSeoValue);
// Format individual Items ///
var seoSetupVal = formatter.format(+currentSeoSetup);
var seoMonthlyVal = formatter.format(+currentSeoValue);
var DesignDepositVal = formatter.format(+currentDesignDeposit);
var DesignPayoffVal = formatter.format(+currentDesignPayoff);
// Apply Current Values /////
$(dueToday).text(currentDueToday);
$(dueLater).text(currentDueLater);
$(dueMonthly).text(currentMonthly);
$(".setupfee span#val").text(seoSetupVal);
$(".seofirstmonth span#val").text(seoMonthlyVal);
$(".seomonthly span#val").text(seoMonthlyVal);
$(".designdeposit span#val").text(DesignDepositVal);
$(".designpayoff span#val").text(DesignPayoffVal);
}
/// Submit Button
$('.packagebuilder').on('submit', function (e) {
e.preventDefault();
var formData = $(this).serialize();
var fullUrl = window.location.href;
var finalUrl = fullUrl + "?&" + formData;
//window.location.href = finalUrl;
alert(finalUrl);
});
.hidden {
opacity: 0;
max-height: 0px;
transition: max-height 0s cubic-bezier(0, 1, 0, 1) 0s;
position:absolute;
top:-100000px;
}
.shown {
opacity: 1;
max-height: 1500px;
transition: max-height 1s ease-in-out 0s;
position:relative;
}
.packagebuilder {
max-width: 700px;
margin: auto;
box-sizing: border-box;
}
form .services {
display:
flex;
/* grid-gap: 30px; */
box-sizing: border-box;
/* padding-bottom: 20px; */
flex-direction: column;
width: auto;
}
form .option {display: flex;grid-gap: 10px;border-radius: 20px;padding-top: 30px;flex-direction: column;}
.services label {
font-size: 22px;
position: relative;
color: black;
font-weight:600!important;
}
.services select {
padding: 15px!important;
height: auto;
font-size: 20px!important;
padding-right: 45px!important;
font-weight: 300;
border-radius: 5px;
appearance: none;
-webkit-appearance:none;
overflow-wrap: break-word;
background: #ffffff;
border: solid 2px #e0e0e0!important;
}
.services select {
background-image: url("https://static1.squarespace.com/static/5ee80dfeb418dc785b48ee76/t/62ec6656067dab5da9df2fa0/1659659862901/angle-down-solid.svg");
background-size: 20px;
background-position: right 15px center;
background-repeat: no-repeat;
}
.services option {}
.services select:focus {
outline:none!important;
}
.estimate {
display: flex;
flex-direction: column;
/* background: linear-gradient(180deg, #eeeeee, transparent); */
margin: 30px 0px 20px;
overflow: hidden;
border-top: solid 1px #000000;
}
.figure {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 0px;
border-bottom: solid 1px #dedede;
}
.figure span:first-child {
font-weight:600
}
.figure span:last-child {
font-size: 20px;
}
.figure:last-child {
border:none;
}
#val {
font-weight:600!important;
padding: 0;
border-radius: 5px;
}
.today #val {font-size: 30px;}
.figure.today {
color: black;
}
.figure {
}
.packagebuilder .disclaimer {
background: #f9f6e6;
padding: 10px;
display: flex;
align-items: center;
justify-content: center;
text-align: left;
font-size: 14px;
grid-gap: 10px;
}
.disclaimer i {
color: #ff9d00;
font-size: 20px;
}
.packagebuilder input.package {
background: #000000!important;
border: none!important;
color: white;
margin-top: 20px!important;
}
.packagebuilder input.package:hover {
background: var(--mainbrand)!important;
}
label.choose {
display:
block;
line-height:40px;
height:40px;
width: fit-content;
cursor: pointer;
min-width: 150px;
font-weight: 300!important;
pointer-events: all;
border-radius: 10px;
-webkit-font-smoothing: antialiased;
margin-top: 10px;
font-family:
Arial,Helvetica,sans-serif;
color: gray!important;
text-align:
center;
background: #e4e4e4;
padding: 1px 15px;
/* box-shadow: 11px 14px 20px #0000001a; */
}
label.add-label {
font-weight:600;
}
input.choose[type=checkbox] {
display: none;
}
input.choose:checked + label {
color: #ffffff!important;
background:
var(--mainbrand);
box-shadow: none;
}
.figure.item {
padding: 0px 20pxpx;
}
.figure.item span, .item span#val {
font-size: 13px;
font-weight: 300!important;
}
.today.item span#val, .today.item span {
color: #656565;
}
.other.item span#val, .other.item span {
color:#6c7674;
}
.today.item {
/* background: #f4f4f4!important; */
}
.other.item {
/* background: #f4f4f4!important; */
}
/* new stuff */
.packcheck {
visibility: hidden;
}
input.choose:checked + label .check {
visibility: visible;
}
input.choose.checkbox:checked + label:before {
content: "";
}
.addservices {
padding-bottom: 20px;
border-bottom: solid 1px #c0c0c0;
}
.flex-options {
display: flex;
justify-content: flex-start;
flex-wrap: wrap;
grid-gap: 10px;
}
:root {
--mainbrand: #006eff;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form class="packagebuilder">
<div class="services">
<div class="addservices">
<label class="add-label">Add Services</label>
<div class="flex-options" name="addservices">
<div>
<input class="choose" id="chooseseo" type="checkbox" name="chooseseo">
<label for="chooseseo" class="choose">SEO</label>
</div>
<div>
<input class="choose" id="choosedesign" type="checkbox" name="choosedesign">
<label for="choosedesign" class="choose">Web Design</label>
</div></div></div>
<div class="seo option hidden">
<label for="seo">Pick your SEO Package</label>
<select type="text" id="seo" name="seo">
<option value="0" disabled selected value>-- Select an Option --</option>
<option value="1" class="one">Gold – $750 per month, $500 setup fee</option>
<option value="2">Essentials – $1500 per month, $1000 setup fee</option>
<option value="3">Local Authority – $2500 per month, $1500 setup fee</option>
</select>
</div>
<div class="design option hidden">
<label for="design">Pick your Design Package</label>
<select type="text" id="design" name="design">
<option value="0" disabled selected value>-- Select an Option --</option>
<option value="1">Vanity – $3000, $1500 deposit</option>
<option value="2">Lux – $5000, $2500 deposit</option>
</select>
</div>
</div><div class="estimate hidden">
<span class="figure today"><span>Due Today: </span><span id="val"></span>
</span><span class="figure today item setupfee" style="display:none;"><span style="">SEO Setup Fee</span><span id="val" style=""></span></span>
<span class="figure today item seofirstmonth" style="display:none;"><span style="">SEO First Month</span><span id="val" style=""></span></span><span class="figure today item designdeposit" style="display:none;"><span style="">Web Design 50% Deposit</span><span id="val" style=""></span></span><span class="figure later"><span>Due Later: </span><span id="val"></span></span><span class="figure other item designpayoff" style="display:none;"><span style="">Web Design Payoff</span><span id="val" style=""></span></span>
<span class="figure monthly"><span>Recurring Starting Next Month: </span><span id="val"></span></span><span class="figure other item seomonthly" style="display:none;"><span style="">SEO Monthly Fee</span><span id="val" style=""></span></span>
</div>
<div class="disclaimer pricing hidden"><i class="fas fa-exclamation-circle"></i>This is only an estimate before the final proposal. The final pricing will be reflected on an e-signature agreement we will send you.</div>
<div class="disclaimer custom" style="
display: none;
"><i class="fas fa-exclamation-circle"></i>Since you chose "Custom" on one of the options, the numbers may not reflect what you have discussed with us. Only choose this option if you spoke with us about a custom package prior.</div><input type="hidden" id="seovalue" name="seovalue" value="">
<input type="hidden" id="seosetup" name="seosetup" value=""><input type="hidden" id="designdeposit" name="designdeposit" value=""><input type="hidden" id="designpayoff" name="designpayoff" value="">
<input type="hidden" id="designtotal" name="designtotal" value=""><input type="submit" value="Continue" class="button package hidden">
</form>
This is following on from:
JS Form validation Checkbox IF statement not working
I've added another separate form for the business owners.
Currently I have a separate html file for 'business' & 'customer'. I've put the CSS for both forms in one file and also the JS is all in one file.
Problem : I seem to only be able to get one form to work at the moment, although if I take the forms out and separate them into different projects they work fine, independently, on different platforms or workspaces.
What I'm trying to do is get the one JS file to reference both forms & execute commands based on what page the user is on filling out the form (business or customer). The problem is only Customer Form works when the JS is all in one file, the Business Form doesn't work (ie show the red highlighted errors). If I take the Business Form out and put it in another stand alone project (inc the HTML, CSS & JS) it works fine. Seems to be a conflict with the Form reference?
I need 2 forms as I will be expanding on fields etc, but to keep things simple I've thrown up samples of both on here.
Here is the HTML for the 'Business' form, (customer form HTML is in the link -and yes it has been changed to work properly):
<div class="business-contactus-body">
<div class="business_container">
<div class="business_contactus_heading_form">
<h2>Business client Form</h2>
</div>
<form id="business_contactus_form" class="business_contactus_form">
<div class="business-form-control">
<label for="businessName">Name</label>
<input type="text" placeholder="ABC Company" id="businessName" />
<i class="fas fa-check-circle"></i>
<i class="fas fa-exclamation-circle"></i>
<small>Error message</small>
</div>
<div class="business-form-control">
<label for="businessEmail">Email</label>
<input type="text" placeholder="a#abccompany.com" id="businessEmail" />
<i class="fas fa-check-circle"></i>
<i class="fas fa-exclamation-circle"></i>
<small>Error message</small>
</div>
<div class="business-form-control">
<label for="businessMessage">Message</label>
<textarea type="text" placeholder="Please enter a brief message" id="businessMessage" cols="30" rows="10"></textarea>
<i class="fas fa-check-circle"></i>
<i class="fas fa-exclamation-circle"></i>
<small>Error message</small>
</div>
<div class="business-form-control">
<input class="businessDisclaimerBox" type="checkbox" id="businessDisclaimerBox" />
<label for="businessDisclaimerBox" id="disclaimer-label">I agree to the terms and conditions.</label>
<small>Error message</small>
</div>
<button class="contactus_form_button">Submit</button>
</form>
</div>
</div>
CSS - for both forms
/***** CONTACT US PAGE CSS *****/
* {
box-sizing: border-box;
}
.customer-contactus-body, business-contactus-body {
min-height: 1300px;
width: 100%;
background-color: pink;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
margin: 0px;
}
.customerCU-contactus-heading-message, .business-contactus-heading-message {
width: 600px;
font-weight: 200;
margin: -50px 0px 50px 20px;
padding: 0px;
}
.customerCU_container, .business_container {
background-color: grey;
border-radius: 5px;
overflow: hidden;
width: 600px;
max-width: 100%;
}
.customer_contactus_heading_form, .business_contactus_heading_form {
border-bottom: 1px solid #f0f0f0;
background-color: white;
padding: 20px 40px;
}
.customer_contactus_heading_form h2, .business_contactus_heading_form h2 {
margin: 0px;
color: black;
}
.contactus_form, .business_contactus_form {
padding: 30px 40px;
}
.customerCU-form-control, .business-form-control {
margin-bottom: 10px;
padding-bottom: 20px;
position: relative;
}
.customerCU-form-control label, .business-form-control label {
display: inline-block;
margin-bottom: 5px;
font-weight: 530;
font-size: 17px;
}
.customerCU-form-control input, .business-form-control input {
border: 2px solid #f0f0f0;
border-radius: 4px;
display: block;
font-size: 14px;
padding: 10px;
width: 100%;
}
.customerCU-form-control input:focus, .business-form-control input:focus {
outline: 0;
border-color: grey;
}
.customerCU-form-control.success input, .business-form-control.success input {
border-color: green;
}
.customerCU-form-control textarea, .business-form-control textarea {
resize: vertical;
border: 2px solid #f0f0f0;
border-radius: 4px;
display: block;
font-size: 14px;
padding: 10px;
width: 100%;
}
.customerCU-form-control.error input, .business-form-control.error input {
border-color: red;
}
.customerCU-form-control textarea:focus, .business-form-control textarea:focus {
outline: 0;
border-color: grey;
}
.customerCU-form-control.success textarea, .business-form-control.success textarea {
border-color: green;
}
.customerCU-form-control.error textarea, .business-form-control.error textarea {
border-color: red;
}
.customerCU-form-control.error label#disclaimer-label, .business-form-control.error label#disclaimer-label {
color: red;
font-weight: bold;
text-decoration: underline;
}
.customerCU-form-control i, .business-form-control i {
visibility: hidden;
position: absolute;
top: 40px;
right: 10px;
}
.customerCU-form-control.success i.fa-check-circle, .business-form-control.success i.fa-check-circle {
color: green;
visibility: visible;
}
.customerCU-form-control.error i.fa-exclamation-circle, .business-form-control.error i.fa-exclamation-circle {
color: red;
visibility: visible;
}
.customerCU-form-control small, .business-form-control small {
color: red;
position: absolute;
bottom: 0;
left: 0;
visibility: hidden;
}
.customerCU-form-control.error small, .business-form-control.error small {
visibility: visible;
}
label#disclaimer-label {
margin-left: 10px;
font-size: 12px;
width: 612px;
}
.contactus_form_button {
background-color: rgb(31, 136, 229);
border: 2px solid rgb(128, 128, 128, 0.199);
border-radius: 4px;
color: #fff;
display: block;
font-size: 16px;
padding: 10px;
margin-top: 20px;
width: 100%;
cursor: pointer;
transition: 0.3s ease background-color;
}
.contactus_form_button:hover {
cursor: pointer;
box-shadow: 1px 1px 1px rgb(25, 60, 173);
}
#keyframes contactus-form-status {
0% {
opacity: 1;
pointer-events: all;
}
90% {
opacity: 1;
pointer-events: all;
}
100% {
opacity: 0;
pointer-events: none;
}
}
JS
//** CUSTOMER FORM **//
const form = document.getElementById('contactus_form');
const customerName = document.getElementById('customerName');
const customerCUEmail = document.getElementById('customerCUEmail');
const disclaimerBox = document.getElementById('disclaimerBox');
form.addEventListener('submit', e => {
e.preventDefault();
checkInputs();
});
function checkInputs() {
// trim to remove the whitespaces
const customerNameValue = customerName.value.trim();
const customerCUEmailValue = customerCUEmail.value.trim();
if(customerNameValue === '') {
setErrorFor(customerName, 'Please enter your name');
} else {
setSuccessFor(customerName);
}
if(customerCUEmailValue === '') {
setErrorFor(customerCUEmail, 'Email cannot be blank');
} else if (!isEmail(customerCUEmailValue)) {
setErrorFor(customerCUEmail, 'Not a valid email');
} else {
setSuccessFor(customerCUEmail);
}
if(!disclaimerBox.checked == true){
setErrorFor(disclaimerBox, 'Please check box and accept our terms and conditions.');
}else {
setSuccessFor(disclaimerBox);
}
}
function setErrorFor(input, message) {
const formControl = input.parentElement;
const small = formControl.querySelector('small');
formControl.className = 'customerCU-form-control error';
small.innerText = message;
}
function setSuccessFor(input) {
const formControl = input.parentElement;
formControl.className = 'customerCU-form-control success';
}
function isEmail(customerCUEmail) {
return /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(customerCUEmail);
}
// ** BUSINESS CLIENT FORM **
const business_contactus_form = document.getElementById('business_contactus_form');
const businessName = document.getElementById('businessName');
const businessEmail = document.getElementById('businessEmail');
const businessMessage = document.getElementById("businessMessage");
const businessDisclaimerBox = document.getElementById('businessDisclaimerBox');
business_contactus_form.addEventListener('submit', e => {
e.preventDefault();
checkbusiness_Inputs();
});
function checkbusiness_Inputs() {
//trim to remove the whitespaces
const businessNameValue = businessName.value.trim();
const businessEmailValue = businessEmail.value.trim();
const businessMessageValue = businessMessage.value.trim();
if (businessNameValue === '') {
setErrorForB(businessName, 'Please enter your name');
} else {
setSuccessForB(businessName);
}
if (businessEmailValue === '') {
setErrorForB(businessEmail, 'Email cannot be blank');
} else if (!isEmailB(businessEmail)) {
setErrorForB(businessEmail, 'Not a valid email');
} else {
setSuccessForB(businessEmail);
}
if (businessMessageValue === '') {
setErrorForB(businessMessage, 'Please enter a message.');
} else {
setSuccessForB(businessMessage);
}
if (!businessDisclaimerBox.checked) {
setErrorForB(businessDisclaimerBox, 'Please check box and accept terms and conditions.');
} else {
setSuccessForB(businessDisclaimerBox);
}
}
function setErrorForB(input, message) {
const formControlB = input.parentElement;
const small = formControlB.querySelector('small');
formControlB.className = 'business-form-control error';
small.innerText = message;
}
function setSuccessForB(input) {
const formControlB = input.parentElement;
formControlB.className = 'business-form-control success';
}
function isEmailB(businessEmail) {
return /^(([^<>()\[\]\\.,;:\s#"]+(\.[^<>()\[\]\\.,;:\s#"]+)*)|(".+"))#((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/.test(businessEmail);
}
I have question about Vue.js. How can i fix this? I didn't find anything in documentation. I've got this error: "[vue/require-v-for-key]
Elements in iteration expect to have 'v-bind:key' directives."
And this: "Elements in iteration expect to have 'v-bind:key' directives."
I have this i my Roulette.vue
<template>
<div class="roulette">
<h1>Roulette</h1>
<div class="radio" v-for="genre in genres"> **here**
<input
#change="onGenrePick"
type="radio"
name="genre"
v-bind:id="genre.id"
v-bind:value="genre.id">
<label v-bind:for="genre.id">{{genre.name}}</label>
</div>
<Button class="btn" :onClick="roll">Roll</Button>
<MovieCard
v-if="!!movie"
v-bind:image="serviceGetImagePath(movie.poster_path)"
v-bind:title="movie.title"
v-bind:releaseDate="serviceFormatYear(movie.release_date)"
v-bind:id="movie.id"
v-bind:voteAverage="movie.vote_average"/>
</div>
</template>
<script>
import MovieCard from '../components/MovieCard'
import Button from '../components/Button'
import {movieService} from '../mixins/movieService'
export default {
name: 'Roulette',
components: {Button, MovieCard},
mixins: [movieService],
mounted: async function () {
this.genres = await this.serviceGetGenres()
},
data: () => ({
genres: [],
pickedGenres: new Set(),
movie: null
}),
methods: {
async roll() {
const genreIds = Array.from(this.pickedGenres)
const movies = await this.serviceGetMoviesByGenre(genreIds)
this.movie = movies[this.getRandom(movies.length)]
},
onGenrePick(e) {
this.pickedGenres.add(e.target.value)
},
getRandom(max) {
return Math.floor(Math.random() * max - 1)
}
}
}
</script>
<style scoped lang="scss">
.roulette {
margin: 40px;
}
.btn {
display: block;
min-width: 220px;
}
.radio {
display: inline-block;
margin: 20px 10px;
> label {
margin-left: 5px;
}
}
</style>
And in my UpcomingMovies.vue also
<template>
<div class="wrapper" v-if="movies.length">
<MovieCard
v-for="movie in movies" **here**
v-bind:image="serviceGetImagePath(movie.poster_path)"**here**
v-bind:title="movie.title"**here**
v-bind:releaseDate="serviceFormatYear(movie.release_date)"**here**
v-bind:id="movie.id"**here**
v-bind:voteAverage="movie.vote_average"/>**here**
<div class="loader">
<Button class="loader__btn" :onClick="loadNextPage">Load</Button>
</div>
<router-link :to="routes.roulette.path">
<div class="roulette">
<img src="../assets/roulette.png" alt="Roulette">
</div>
</router-link>
</div>
<Loader v-else/>
</template>
<script>
import Button from '../components/Button'
import MovieCard from '../components/MovieCard'
import Loader from '../components/Loader'
import { movieService } from '../mixins/movieService'
import routes from '../router/routes'
export default {
name: 'UpcomingMovies',
mixins: [movieService],
components: { Button, MovieCard, Loader },
data: () => ({
movies: [],
page: 1,
routes
}),
mounted() {
this.getMovies(this.page)
},
methods: {
getMovies: async function (page) {
const movies = await this.serviceGetMovies(page)
this.movies.push(...movies)
},
loadNextPage() {
this.getMovies(++this.page)
}
}
}
</script>
<style scoped lang="scss">
.wrapper {
width: 100%;
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
margin-top: 40px;
margin-bottom: 40px;
}
.loader {
width: 100%;
text-align: center;
margin-top: 40px;
margin-bottom: 40px;
&__btn {
border: 5px dashed white;
background-color: transparent;
border-radius: 50%;
width: 80px;
height: 80px;
font-weight: bold;
text-transform: uppercase;
transition: border-radius 100ms ease-in-out, width 120ms ease-in-out 120ms;
&:hover {
border-radius: 0;
background-color: rgba(white, 0.1);
width: 200px;
}
}
}
.roulette {
cursor: pointer;
position: fixed;
right: 25px;
bottom: 25px;
> img {
opacity: .8;
animation: rotate 5s infinite;
width: 70px;
height: auto;
transition: opacity 220ms linear;
&:hover {
opacity: 1;
}
}
}
#keyframes rotate {
0% {
transform: rotate(0);
}
100% {
transform: rotate(360deg);
}
}
</style>
Vue internally uses unique keys for each loop to determine which element to update at which point during an update process. Therefore every v-for needs a v-bind:key attribute to work properly. In your special case this would be as the following:
<div class="radio" v-for="genre in genres" v-bind:key="someUniqueId"> **here**
You can use the current loop index as ID or anything else.
Here is what I try to acomplish: I need an input field containing a value with a unit, that would look like this:
On focussing the input, I want it to move the unit to the right side, looking like this:
I can think of two ways to do so:
1. Replace input field with a Div that looks exactly like the input when focus is lost, and set the value of the input as its content:
$('#fakeInput').bind('click', changeToRealInput);
$('#realInput').bind('blur', changeToFakeInput);
$('#realInput').trigger('blur');
$('#unitAddon').html($('#realInput').attr('unit'));
function changeToFakeInput() {
// hide actual input and show a div with its contents instead
$('#fakeInput').show();
$('#realInputContainer').hide();
$('#fakeInput').html($('#realInput').val() + $('#realInput').attr('unit'));
}
function changeToRealInput() {
// hide fake-div and set the actual input active
$('#fakeInput').hide();
$('#realInputContainer').show();
$('#realInput').focus();
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
div#container {
display: flex;
background: #8aaac7;
padding: 10px;
width: 200px;
}
div#unitAddon,
input#realInput,
div#fakeInput {
font-family: sans-serif;
font-size: 26px;
padding: 5px;
width: 100%;
background-color: #FFFFFF;
border: none;
outline: none;
}
div#realInputContainer,
div#fakeInput {
border: 2px solid #dadada;
}
div#realInputContainer {
display: flex;
}
div#unitAddon {
width: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="container">
<div id="fakeInput"></div>
<div id="realInputContainer">
<input type="number" unit="kg" id="realInput" value="3.3">
<div id="unitAddon"></div>
</div>
</div>
(also see this jsFiddle)
Problem here is (as you can see in the screenshot above) that, depending on your local settings, chrome automatically converts the decimal point into a comma (in the input, but not in the fake-div)
Another way I thought of is: When the focus is lost, set the size of the input field to match its content and, by doing so, pull the addon displaying the unit just behind the number.
Problem here is to get the size of the content of an input (cross-browser):
$('#realInput').bind('focus', changeToRealInput);
$('#realInput').bind('blur', changeToFakeInput);
$('#realInput').trigger('blur');
$('#unitAddon').html($('#realInput').attr('unit'));
function changeToFakeInput() {
// here is the question: what width should it be?
$('#realInput').css({'width' : '40%'});
}
function changeToRealInput() {
$('#unitAddon').css({'width' : 'auto'});
$('#realInput').css({'width' : '100%'});
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
div#container {
display: flex;
background: #8aaac7;
padding: 10px;
width: 300px;
}
div#unitAddon,
input#realInput{
font-family: sans-serif;
font-size: 26px;
padding: 5px;
width: 100%;
border: none;
outline: none;
}
div#realInputContainer {
border: 2px solid #dadada;
display: flex;
background-color: #FFFFFF;
}
div#realInputContainer.setAddonAway > div#unitAddon {
width: auto;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.0/jquery.min.js"></script>
<div id="container">
<div id="realInputContainer" class="setAddonClose">
<input type="number" unit="kg" id="realInput" value="3.3">
<div id="unitAddon"></div>
</div>
</div>
also see this jsFiddle
I could accomlish this with an input[type=text], but I dont want to loose the benefits of type[number] (min/max/step validation, on-screen keyboard, etc.)
Is there any way of getting around the flaws of my two ideas? Or is thre a more elegant way to do so?
The idea is to: (1) make the input box to cover the entire container; (2) create a helper element, and set it the same length as the input value via JS, and make it invisible as a place holder; (3) apply some style for moving around the unit box.
codepen
$(document).ready(function() {
$(".value").text($(".number").val());
$(".unit").text($(".number").attr("unit"));
$(".number").on("change keypress input", function() {
$(".value").text($(".number").val());
});
});
* {
box-sizing: border-box;
}
input::-webkit-outer-spin-button,
input::-webkit-inner-spin-button {
-webkit-appearance: none;
margin: 0;
}
.container {
position: relative;
display: flex;
border: 4px solid teal;
width: 200px;
}
.container > * {
font-family: sans-serif;
font-size: 20px;
}
.number {
position: absolute;
left: 0;
top: 0;
width: 100%;
padding: 0;
border: 0;
outline: 0;
background: transparent;
}
.value {
visibility: hidden;
max-width: 100%;
overflow: hidden;
}
.unit {
position: relative;
flex: 1;
pointer-events: none;
background: white;
}
.number:focus ~ .value {
flex: 1;
}
.number:focus ~ .unit {
flex: 0;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="container">
<input class="number" type="number" value="1.23" unit="kg">
<span class="value"></span>
<span class="unit"></span>
</div>