How can I listen for events on a disabled element? I have an input box that I have disabled but that I want to enable if the user double clicks it. I know this is possible using a label and switching the label off using CSS. I want to know if there is a way to do this without a label - is there some way to disable the input and still handle events for it? or is there a way to make the input unfocusable unless double-clicked?
You can prevent the default action of the input with a timeout. If a user clicks before the ms has elapsed, you run the desired code:
new Vue({
el: '#app',
data: {
checked: false,
timeoutId: null, // You do not need to have this defined, vue will initialize it for you, but here for clarity
},
methods: {
dblClick(event) {
// Simple click
if (!this.timeoutId) {
event.preventDefault();
event.stopPropagation();
this.timeoutId = setTimeout(() => {
this.timeoutId = null;
}, 300);
return // Do not run below code if it is a single click
}
// double click
this.timeoutId = null;
console.log('dblClick')
}
}
});
#app {
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 100%;
height: 100vh;
}
h1 {
font-size: 1.5em;
margin-bottom: 5px;
}
i {
font-size: .75em;
margin: 0;
color: #969696;
}
input {
transform: scale(2);
margin: 20px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>Checkbox is {{ checked ? 'checked' : 'not checked' }}</h1>
<i>Double-click to change</i>
<input v-model="checked" type="checkbox" #click="dblClick">
</div>
Related
I have a form that I'm using for orders. I'm using the same form for every order but I want to replace the last word in the text string of the header (in this case "Food") to show what the customer is ordering based on the button they clicked. For this example, I've just made a simple layout with three buttons that are each assigned a value inside their button tags. The same button style is going to be used all throughout, it's just going to have a different name and value assigned to it for the form. I've also made a close button to simulate the user closing the form and forcing the last word in the header back to its default Food just in case they manage to open the form without actually clicking a button so they don't see something like "Order Your null". So ideally, the user would click their choice of food and the header title would change to "Order Your [Food Value]"
The problem I'm having is that I can't figure out how to collect the values and change the innerHTML based on what was clicked using javascript. If it were only three buttons I could write three separate expressions but on my project, I've got about 40 items and buttons total so I'd rather label 40 buttons with values and use one script to power them all as opposed to writing an expression for every button (completely possible but not very efficient for myself or the user's browser I would imagine...).
The script runs but returns an "undefined" value. I've tried changing the let food = document. attribute to just about anything I could find on Google and nothing is returning a value (or an error for that matter).
https://jsfiddle.net/astombaugh/kf2vgq0p/550/
<body>
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="foodHeader">Order Your<div id="foodHeader">Food</div>
</div>
<div class="btnContainer">
<a class="orderBtn" name="foodItem" value="Cheeseburger">Order Your Cheeseburger</a>
<a class="orderBtn" name="foodItem" value="Salad" style="background-color: green">Order Your Salad</a>
<a class="orderBtn" name="foodItem" value="Sub" style="background-color: blue">Order Your Sub</a>
</div>
<div class="closeBtnContainer">
<a class="closeBtn">
X
</a>
</div>
</body>
.foodHeader {
display: block;
font-family: Helvetica Neue, Helvetica, Arial;
font-weight: 700;
text-align: center;
color: black;
font-size: 3rem;
margin: 0 auto;
}
.btnContainer {
display: flex;
flex-direction: row;
}
.orderBtn {
display: block;
font-family: Helvetica Neue, Helvetica, Arial;
font-weight: 700;
text-align: center;
color: white;
width: 200px;
height: 50px;
margin: 0 auto;
background-color: red;
font-size: 1rem;
padding: 10px 10px;
line-height: 3rem;
outline: 1px solid black;
}
.closeBtnContainer {
display: block;
margin: 20px auto;
text-align: center;
}
.closeBtn{
background-color: black;
color: white;
width: 20px;
height: 20px;
margin: 0px auto;
padding: 10px;
text-align: center;
cursor: pointer;
}
let food = document.querySelectorAll('foodItem').value;
let foodHeaderText = document.getElementById('foodHeader');
const orderBtn = document.getElementsByClassName('orderBtn');
const closeBtn = document.getElementsByClassName('closeBtn');
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function() {
foodHeaderText.innerHTML = food;
})
}
for (let close = 0; close < closeBtn.length; close++) {
closeBtn[close].addEventListener('click', function() {
foodHeaderText.innerHTML = "Food";
})
}
The function you pass to the event listeners accepts a parameter, that is the event object. You can find more here. Given that, you can arrange a solution like the following:
let foodHeaderText = document.getElementById('foodHeader');
const orderBtn = document.getElementsByClassName('orderBtn');
const closeBtn = document.getElementsByClassName('closeBtn');
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function(event) {
foodHeaderText.innerHTML = event.target.attributes["value"]["value"];
})
}
for (let close = 0; close < closeBtn.length; close++) {
closeBtn[close].addEventListener('click', function(event) {
foodHeaderText.innerHTML = "Food";
})
}
Reason why you are getting undefined is because
let food = document.querySelectorAll('foodItem').value;
definitely is undefined: querySelectorAll return a collection, and has no property value. Besides, you want to dynamically get the value at each click, not once for all.
Replace code buy:
function(e) {
foodHeaderText.innerHTML = e.target.getAttribute("value") || "food";
I believe your issue is in this line:
let food = document.querySelectorAll('foodItem').value;
You could try this:
let food = Array.from(document.querySelectorAll('[name="foodItem"]')).map(el => el.getAttribute('value'))
Hope this helps.
food isn't defined as a string. In fact, it's undefined because you're trying to retrieve all foodItem elements at once before they're even clicked. As others have said - that's a NodeList and won't have a value property.
You code can be updated to:
for (let order = 0; order < orderBtn.length; order++) {
orderBtn[order].addEventListener('click', function(e) {
foodHeaderText.innerHTML = e.target.value;
});
}
Or, to be a little more modern:
document.getElementsByClassName("orderBtn").forEach(orderBtn => {
orderBtn.addEventListener("click", e => {
foodHeaderText.textContent = e.target.value;
});
});
Or, even better, don't use tons of listeners - just add one to the parent container:
document.querySelector(".btnContainer").addEventListener("click", e => {
if (e.target.matches(".orderBtn")) {
foodHeaderText.textContent = e.target.value;
}
}
your code should work
// just change this line
foodHeaderText.innerHTML = food;
// with this one
foodHeaderText.innerHTML = this.innerHTML;
// or with this one
foodHeaderText.innerHTML = orderBtn[order].innerHTML;
and get rid of that food variable
It's not correct, and you don't have any foodItem classes in your html file, even though you've already selected them as orderBtn.
What I have
A custom DropDown with a filter text input above. The DropDown can be opened independently from the filter text input.
What I want
The intended behavior would be, that the dropdown closes when the filter input loses focus and also when I click with the mouse outside of the DropDown, so that the DropDown loses the focus.
What I tried
Bind to the blur event on my root div element in the control, which doesn't fire at all.
I also couldn't find anything about internal component methods which I could override.
Code
<div #blur="onRootLostFocus">
...
</div>
...
...
...
onRootLostFocus() {
console.log('LostFocus');
this.deactivateSearchPanel();
this.deactivateSelectionPanel();
}
Solution
I missed, that a div needs tabindex="0" to be focusable, this fixed my problem
Something like this?
Answer: You need to set tabindex="0" to make it focusable.
Here an custom dropdown how you could do it:
Vue.component("dropdown", {
props: ["value"],
data(){
return {
open: false,
options: ["BMW", "Fiat", "Citroen", "Audi", "Tesla"]
}
},
methods: {
toggleDropdown() {
this.open = !this.open;
},
closeDropdown(){
this.open = false;
},
selectOption(option) {
this.$emit("input", option);
}
},
template: `<div class="dropdown">
<div #blur="closeDropdown" tabindex="0" ref="dropdown" #click="toggleDropdown" class="select">
{{ value }}
</div>
<div class="options" :style="{'max-height': open ? '300px' : '0px'}">
<div v-for="option in options" #mousedown="selectOption(option)" class="option">
{{ option }}
</div>
</div>
</div>`
})
new Vue({
el: "#app",
data: {
selectedCar: "BMW"
}
})
.dropdown {
width: 200px;
position: relative;
}
.select {
height: 40px;
position: absolute;
left: 0;
width: 100%;
background: green;
display: flex;
justify-content: center;
align-items: center;
color: white;
cursor: pointer;
}
.option {
width: 100%;
height: 40px;
background: darkgreen;
color: white;
display: flex;
justify-content: center;
align-items: center;
cursor: pointer;
}
.option:hover {
background: green;
}
.options {
overflow: hidden;
transition: max-height 200ms;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app"> <p>
{{ selectedCar }}</p>
<dropdown v-model="selectedCar" />
</div>
Note there is also tabindex="-1" to make it not reachable via sequential keyboard navigation.
Also consider using a <button> instead because of accessibility concerns.
See https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/tabindex
I'm trying to make a overflow with tagging, which fades out in the beginning to give the user a hint that there's more. This is what it looks like:
I put the fading gradient as a :after inside the CSS and "activate" it by Vue's style binding, when scrollWidth > offsetWidth (overflow bigger than the box itself).
But the problem is that it sometimes (lags?) behind and does not calculate the scrollWidth right, especially when I enter a long word and then delete it. It doesn't "like" that and it says that the overflow is false, but there's no tag in the box. Basically this happens:
I tried to put the calculation inside this $nextTick(), but it didn't solve the issue. I also tried using Vue's keyDown, keyUp and keyPress listeners, but nothing solved this also.
This (also on CodePen) demonstrates the issue:
new Vue({
el: '#tagsinput',
data: {
input_value: "",
tags: []
},
methods: {
addTag: function() {
if (this.input_value > "") {
this.tags.push(this.input_value)
this.input_value = "";
// Refocus the text input, so it stays at the end
this.$refs.input.blur();
this.$nextTick(function() {
this.$refs.input.focus();
})
}
},
deleteTag: function() {
if (this.input_value == "") {
this.tags.pop()
}
}
}
})
.outter {
border: 1px solid red;
width: 250px;
overflow: hidden;
display: flex;
}
.inner {
border: 1px solid blue;
margin: 2px;
display: flex;
}
.tag {
border: 1px solid black;
margin: 2px;
}
input {
min-width: 80px;
width: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.6.2/vue.min.js"></script>
<div id="tagsinput">
<div class="outter" ref="outter">
<div class="inner" ref="inner">
<div class="tag" v-for="tag in tags">{{tag}}</div><input type="text" v-model="input_value" #keydown.enter="addTag" #keydown.delete="deleteTag">
</div>
</div>
Outter div scrollwidth: {{ $refs.outter ? $refs.outter.scrollWidth : null }}<br> Outter div offsetWidth: {{ $refs.outter ? $refs.outter.offsetWidth : null }}<br>
<br> Is overflowing: {{ ($refs.outter ? $refs.outter.scrollWidth : null) > ($refs.outter ?$refs.outter.offsetWidth : null) }}
</div>
<br><br> Type a really long word in, add and then delete it. "Is overflowing" will be the inverse, until you press Backspace <b>again</b>.
Any help is greatly appreciated.
You should call the check for overflow after the moment you've added or deleted the tag, so you check the overflow at the right moment. Vue isn't databinding an inline condition like that. The following code should work for you. It calls a checkOverflow function within $nextTick, setting a data-binded variable isOverflowed that you then can use to bind some styles.
new Vue({
el: '#tagsinput',
data: {
input_value: null,
tags: [],
isOverflowed: false
},
methods: {
addTag: function() {
if(this.input_value) {
this.tags.push(this.input_value)
this.input_value = null;
// Refocus the text input, so it stays at the end
this.$refs.input.blur();
this.$nextTick(function() {
this.$refs.input.focus();
this.checkOverflow()
})
}
},
deleteTag: function() {
if(!this.input_value) {
this.tags.pop()
this.$nextTick(function() {
this.checkOverflow()
})
}
},
checkOverflow: function() {
this.isOverflowed = (this.$refs.outter ? this.$refs.outter.scrollWidth : null) >
(this.$refs.outter ? this.$refs.outter.offsetWidth : null)
}
}
})
.outter {
border: 1px solid red;
width: 250px;
overflow: hidden;
display: flex;
}
.inner {
border: 1px solid blue;
margin: 2px;
display: flex;
}
.tag {
border: 1px solid black;
margin: 2px;
}
input {
min-width: 80px;
width: 80px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="tagsinput">
<div class="outter" ref="outter">
<div class="inner" ref="inner">
<div class="tag" v-for="tag in tags">{{tag}}</div>
<input type="text" v-model="input_value" #keydown.enter="addTag" #keydown.delete="deleteTag" ref="input">
</div>
</div>
<br>
Is overflowing:
{{ isOverflowed }}
</div>
<br><br>
Type a really long word in, add and then delete it. "Is overflowing" will be the inverse, until you press Backspace <b>again</b>.
More of a CSS/HTML hack ...
Add <div id="spaceFade"></div> after <div id="tagsinput">, then add the following CSS :
#spaceFade {
background-image: linear-gradient(to right, rgba(255,255,255,1), rgba(255,255,255,1), rgba(0,0,0,0));
position : absolute;
height : 2em;
width : 3em;
}
#tagsinput {
position : relative;
}
.outter {
justify-content: flex-end;
}
I want to assign "active" to a button which I clicked on and remove active from the other buttons when I do so, I want to do this in Vanilla JS since I want to practice my skills. I have tried out similar issues on SO, however I can't get my code to work.
Ultimately I want to create a gallery filter for images taken from my DB in PHP which I added to my code as seen bellow based on button which is acive I want to show / hide the categories. The image receive a class = someCategory from the image_category column in my DB.
Help is appreciated.
I've tried to take similar issues under consideration. But I failed to make it work.
Vanilla JS remove class from all other elements besides 'active' class
HTML & PHP CODE:
<div id="category-buttons">
<h5>Choose image category</h5>
<button id="All" class="catgory-button active" >All Categories</button>
<button id="CNC_Machining_button" class="catgory-button">CNC_Machining</button>
<button id="HP_Die_Casting" class="catgory-button">HP Die Casting</button>
<button id="Iron_Casting" class="catgory-button">Iron Casting</button>
<button id="Steel_Casting" class="catgory-button">Steel Casting</button>
<button id="Precision_Casting" class="catgory-button">Precision Casting</button>
<button id="Aluminium_Casting" class="catgory-button">Aluminum Casting</button>
</div>
<section class="gallery-links">
<div class="wrapper">
<h2 class="product-gallery-title">Product Gallery</h2>
<div class="gallery-container">
<?php
include_once 'includes/dbh.inc.php';
$sql = 'SELECT * FROM gallery ORDER BY orderGallery DESC';
$stmt = mysqli_stmt_init($conn);
if (!mysqli_stmt_prepare($stmt,$sql)) {
echo 'SQL statement failed!';
} else {
mysqli_stmt_execute($stmt);
$result = mysqli_stmt_get_result($stmt);
while ($row = mysqli_fetch_assoc($result)) {
//what's echoed out by the database
echo ' <a class="images '.$row["image_category"].'" style="background-repeat:no-repeat; background-image: url(gallery/'.$row["imgFullNameGallery"].')">
<div class="color-overlay">
<h3>'.$row["titleGallery"].'</h3>
<p>'.$row["descGallery"].'</p>
</div>
</a>';
}
}
?>
</div>
</div>
<?php
JS Code:
let btns = Array.from(document.querySelectorAll('.category-button'));
const handleClick = (e) => {
e.preventDefault();
btns.forEach(node => {
node.classList.remove('active');
});
e.currentTarget.classList.add('active');
}
btns.forEach(node => {
node.addEventListener('click', handleClick)
});
Relevant CSS CODE:
#category-buttons {
float: left;
display: flex;
flex-wrap: wrap;
flex-direction: column;
width: 12.5%;
margin: 10vh 10px;
}
.category-button {
padding: 20px;
margin: 2px 0;;
color: white;
background: rgb(153, 124, 124);
}
.product-gallery-title {
text-transform: uppercase;
}
.wrapper {
text-align: center;
width: 65%;
margin: auto;
}
.gallery-container {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-gap: 13px;
margin: auto;
width: 100%;
background: rgb(236, 236, 236);
justify-content: center;
}
.images{
height: 300px;
width: 300px;
max-width: 300px;
max-height: 300px;
text-decoration: none;
}
Expected Result:
On click, button receives active class and other buttons in scope lose them.
What you could do for example is to store the active button in a global variable and use that variable to remove the class from it when another button is clicked.
So something like this:
let btns = Array.from(document.querySelectorAll('.category_btn'));
let activeButton = null;
// The button which would be used to add 'active' class to
// all the buttons.
let allButton = document.querySelector('#All');
const handleClick = (e) => {
e.preventDefault();
e.currentTarget.classList.add('active');
// Checks that the activeButton is defined (initially it's null).
// Also checks that the button that was clicked is not the button which is
// already active to avoid removing the active class on double click.
if (activeButton != null && activeButton != e.currentTarget) {
activeButton.classList.remove('active');
}
activeButton = e.currentTarget;
}
btns.forEach(node => {
node.addEventListener('click', handleClick)
});
// Listen to a click event from the "allButton" and when it's clicked,
// loop through the buttons array and add 'active' class to all buttons
// in it.
allButton.addEventListener('click', function() {
btns.forEach(btn => {
btn.classList.add('active');
})
});
I'm not currently anywhere where I can test and verify that it works, but I see no reason why it would not work.
I have a custom element which takes input whenever user clicks on it I want to focus on it and create an overlay on other elements and when the user clicks outside the div I want to remove the overlay.
I am trying to do it using iron-overlay-behavior but not able to achieve the expected behavior.
<custom-element
with-backdrop
scroll-action="lock"
clicked="{{isClicked}}"
></decision-view>
All the examples shown are mostly for paper-dialog but how can I use iron-overlay-behavior for a custom element of my own?
The iron-overlay-behavior seems to be meant more for modal dialogs, what you are trying to accomplish is something different (for instance, modal dialogs are only shown one at a time, and require user input before going back to normal application/website behavior). So I think a natural thing for that behavior would be to disable anything else to focus!
When you say:
create an overlay on other elements
what does that mean? Just paint white over them like they were not visible?
I had a similar issue last week. See Showing a gray backdrop with a mixin
.
For a demo, see this pen from Makha:
<dom-module id="parent-component">
<template>
<style>
:host {
display: block;
margin: 10px auto auto auto;
border: 2px solid gray;
border-radius: 8px;
background-color: white;
padding: 5px;
width: 100px;
}
[hidden] {
display: none;
}
paper-button {
background-color: lightblue;
}
#placeholder {
width: 120px;
height: 150px;
}
</style>
<div>Try me.</div>
<paper-button on-tap="_doTap">Push</paper-button>
<div id="placeholder" hidden></div>
</template>
<script>
class ParentComponent extends Polymer.Element {
static get is() { return 'parent-component'; }
static get properties() {
return {
mychild: {
type: Object
}
}
}
_doTap(e) {
let x = (e.detail.x - 50) + 'px';
let y = (e.detail.y - 50) + 'px';
this.mychild = new MyChild();
this.mychild.addEventListener('return-event', e => this._closeChild(e));
this.$.placeholder.style.position = 'absolute';
this.$.placeholder.appendChild(this.mychild);
this.$.placeholder.style.left = x;
this.$.placeholder.style.top = y;
this.$.placeholder.removeAttribute('hidden');
this.mychild.open();
}
_closeChild(e) {
console.log('Child says '+e.detail);
this.mychild.remove();
this.mychild = null;
this.$.placeholder.setAttribute('hidden', '');
}
}
customElements.define(ParentComponent.is, ParentComponent);
</script>
</dom-module>
<parent-component></parent-component>
<dom-module id="my-child">
<template>
<style>
:host {
display: block;
margin: 10px auto auto auto;
border: 2px solid gray;
border-radius: 8px;
background-color: white;
padding: 15px;
}
paper-button {
background-color: lightgray;
}
</style>
<div>I'm a child.</div>
<paper-button on-tap="_doTap">Close</paper-button>
</template>
<script>
class MyChild extends Polymer.mixinBehaviors([Polymer.IronOverlayBehavior], Polymer.Element) {
static get is() { return 'my-child'; }
static get properties() {
return {
withBackdrop: {
type: Boolean,
value: true
}
}
}
ready() {
super.ready();
console.log("Daddy?");
}
_doTap(e) {
this.dispatchEvent(new CustomEvent('return-event',
{ detail: 'Bye!', bubbles: true, composed: true }));
}
}
customElements.define(MyChild.is, MyChild);
</script>
</dom-module>