Trying to replicate on of the iron-form demos from https://www.webcomponents.org/element/PolymerElements/iron-form/demo/demo/index.html inside a Polymer 2 element, and just can't seem to get it working.
When clicking Submit, I get Uncaught ReferenceError: _delayedSubmit is not defined. Any suggestions?
JSBin: https://jsbin.com/pinasum/edit?html,console,output
Code:
<dom-module id="spp-login">
<template>
<iron-form id="loginForm">
<form action="/login" method="post">
<paper-input name="username" label="Username" required auto-validate></paper-input>
<paper-input name="password" label="Password" required auto-validate></paper-input>
<paper-button raised onclick="_delayedSubmit(event);" disabled id="loginFormSubmit">
<paper-spinner id="spinner" hidden></paper-spinner>
Submit
</paper-button>
<paper-button raised onclick="loginForm.reset();">Reset</paper-button>
</form>
<div class="output"></div>
</iron-form>
</template>
<script>
class SppLogin extends Polymer.Element {
static get is() {
return 'spp-login';
}
static get properties() {
return {
username: String,
password: String,
};
}
connectedCallback() {
super.connectedCallback();
const loginForm = this.$.loginForm;
const spinner = this.$.spinner;
const loginFormSubmit = this.$.loginFormSubmit;
loginForm.addEventListener('iron-form-submit', (event) => {
this.querySelector('.output').innerHTML = JSON.stringify(event.detail);
spinner.active = false;
spinner.hidden = true;
loginFormSubmit.disabled = false;
});
loginForm.addEventListener('change', (event) => {
loginFormSubmit.disabled = !loginForm.validate();
});
loginForm.addEventListener('iron-form-presubmit', (event) => {
event.preventDefault();
console.log('here');
});
}
_delayedSubmit(event) {
const loginForm = this.$.loginForm;
const spinner = this.$.spinner;
spinner.active = true;
spinner.hidden = false;
loginForm.disabled = true;
// Simulate a slow server response.
setTimeout(function() {
loginForm.submit();
}, 1000);
}
}
window.customElements.define(SppLogin.is, SppLogin);
</script>
</dom-module>
To add event listeners to DOM elements, you have to use on-event annotations in your template.
Also, whether you provide a name for the event object or not, the event object is already being passed to your callback.
Polymer doesn't support passing arguments in the event attributes.
The examples shown in the iron-form demo page is using demo-snippet, that works both for native elements as well as polymer elements.
So, you need to change your code from:
onclick="_delayedSubmit(event);"
to: on-click="_delayedSubmit".
when triggering an event with paper button you have to use on-click and you can't specify the parameters.
So the correct syntax would be on-click="_delayedSubmit"
Related
Based on this Article https://medium.com/#Taha_Shashtari/an-easy-way-to-detect-clicks-outside-an-element-in-vue-1b51d43ff634
i implemented the same methodology of the directive for detecting outside element click, at first i had to change things as vue 2 directives have been changed in vue 3, but i got so far that:
When i click the Icon to Toggle the Box -> The box is shown
When i click outside the Box -> The box is toggled
The only thing that isn't working is when i click inside the box itself it gets toggled again, which isnt suppose to happen.
Code
Directive:
let handleOutsideClick;
const closable = {
beforeMount(el, binding, vnode) {
handleOutsideClick = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
binding.instance[handler]();
}
};
document.addEventListener("click", handleOutsideClick);
document.addEventListener("touchstart", handleOutsideClick);
},
afterMount() {
document.removeEventListener("click", handleOutsideClick);
document.removeEventListener("touchstart", handleOutsideClick);
},
};
export default closable;
PS: I changed the usage of refs into IDs
CartIcon:
<template>
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
v-closable='{
exclude: ["checkoutBox","checkoutBoxHandler"],
handler: "onClose",
}'
id="checkoutBox"
>
<CheckOutBox v-if="this.userCart" :userCart="this.userCart"></CheckOutBox>
</div>
</div>
</template>
onClose handler:
onClose() {
this.showPopup = false;
},
Can anyone see what i might be doing wrong here or maybe missing?
Thanks in advance
EDIT after Turtle Answers:
This is the Code i m using:
Directive:
const clickedOutsideDirective = {
mounted(element, binding) {
const clickEventHandler = (event) => {
event.stopPropagation();
console.log(element.contains(event.target))//True on click on the box
if (!element.contains(event.target)) {
binding.value(event)
}
}
element.__clickedOutsideHandler__ = clickEventHandler
document.addEventListener("click", clickEventHandler)
},
unmounted(element) {
document.removeEventListener("click", element.__clickedOutsideHandler__)
},
}
export default clickedOutsideDirective
Component:
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
v-closable='onClose'
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
ref="checkoutBox"
id="checkoutBox"
>
<CheckOutBox :userCart="this.userCart"></CheckOutBox>
</div>
</div>
The box is being displayed but on click on the box it still disappear
It looks like the problem could be multiple registered event listeners.
afterMount should be unmounted. If fixing that isn't enough, you may need to ensure you're unregistering the event correctly. You can store the handler on the element like this:
const closable = {
beforeMount(el, binding, vnode) {
el.__handleOutsideClick__ = (e) => {
e.stopPropagation();
const { handler, exclude } = binding.value;
let clickedOnExcludedEl = false;
exclude.forEach((id) => {
if (!clickedOnExcludedEl) {
const excludedEl = document.getElementById(id);
clickedOnExcludedEl = excludedEl.contains(e.target);
}
});
if (!el.contains(e.target) && !clickedOnExcludedEl) {
binding.instance[handler]();
}
};
document.addEventListener("click", el.__handleOutsideClick__);
document.addEventListener("touchstart", el.__handleOutsideClick__);
},
// The correct lifecycle method is 'unmounted'
unmounted(el) {
document.removeEventListener("click", el.__handleOutsideClick__);
document.removeEventListener("touchstart", el.__handleOutsideClick__);
},
};
export default closable;
Other advice
Don't call stopPropagation on the event, because it could swallow clicks on other UI elements.
Forward the event when invoking the handler so that the handler can inspect it.
To ensure your directive doesn't break, you probably don't want to reference the excluded nodes by ID, but rather by ref as in the article you linked.
Or, drop the exclusions feature altogether. Without it, your directive can look like below. It looks like you're only using it to exclude things that are already inside your popup. In my experience, clicked outside should mean clicked outside. If there are additional considerations, I would prefer to let the handler take care of them by inspecting the returned event.
import { Directive } from 'vue'
// Trigger a function when a click is registered outside the element
const clickedOutsideDirective = {
mounted(element, binding) {
const clickEventHandler = (event) => {
if (!element.contains(event.target)) {
binding.value(event)
}
}
element.__clickedOutsideHandler__ = clickEventHandler
document.addEventListener("click", clickEventHandler)
},
unmounted(element) {
document.removeEventListener("click", element.__clickedOutsideHandler__)
},
}
export default clickedOutsideDirective
Now the usage looks like this
<template>
<div
id="checkoutBoxHandler"
ref="checkoutBoxHandler"
#click="showPopup = !showPopup"
class="cart-icon"
>
<font-awesome-icon icon="fa-solid fa-cart-shopping" />
<span id="cart-summary-item">{{ cartItemsCount }}</span>
<div
v-show="showPopup"
v-clicked-outside='onClose'
id="checkoutBox"
>
<CheckOutBox v-if="this.userCart" :userCart="this.userCart"></CheckOutBox>
</div>
</div>
</template>
For me the best solution for this problem is to create some object in the background.
position:fixed;
top:0;
left:0;
width: 100vw;
height: 100vh;
z-index: check which value fits you here.
So at beginning before showing "box" that object do not exist. On box show, you also show that object which is in background, above all elements except your "box".
So only thing you can click outside of your "box" is that object. And you can put event on "that object click".
And on box hide, you also hide that object;
I have a very simple livewire component
class Test extends Component
{
public $test = "test";
public function submit()
{
dd($this->test);
}
public function render()
{
return view('livewire.test');
}
}
and view
<div>
<form wire:submit.prevent="submit" method="post">
<input type="text" wire:model="test" id="test">
<button type="button" id="ok">ok</button>
<button type="submit">submit</button>
</form>
<script>
const button = document.getElementById('ok');
button.addEventListener('click', function() {
const input = document.getElementById('test');
input.value = "Test";
});
</script>
</div>
I simplified the code for illustrative purposes to show that JavaScript changes a value.
When I click ok what changes the value from test to Test and then submit, I get test shown instead of Test.
I except to see Test. What am I doing wrong?
It seems that the Livewire Component is not recognising the change from the Javascript code, as the following function doesn't fire either:
public function updatedTest()
{
dd("Fired");
}
What #Peppermingtology said isn't entirely true. He is right that it is not the preferred way of handling Livewire, but sometimes you can't escape it.
You can indeed use AlpineJS and that will solve your issue, but you can also cast a normal change event. Your Livewire variable isn't updating because Livewire has not detected any change. That's because programattically updated inputs don't trigger any event by default to prevent infinite event loops.
If you simply add an event, it should also work:
const input = document.getElementById('test');
input.value = "Test";
let event = new Event('change', {bubbles: true});
input.dispatchEvent(event);
You can't manipulate properties in that manner with Livewire. The preferred way is to use the AlpineJS entangle method which specifically caters for sharing state between Livewire and your client.
That being said, you can achieve what you're after without using AlpineJS. Replacing your existing JavaScript with the below should get you the result you're after.
<script>
document.addEventListener('livewire:load', (evt) => {
const button = document.getElementById('ok');
button.addEventListener('click', function() {
document.getElementById('test').value = #this.test = 'Test';
});
})
</script>
I have following code, where, based on event, I add some html code. I would like to refer to 'id' from this dynamically injected html in other event (or just from other part of the code):
<div id="choice"></div>
var decisionList = document.getElementById("decisionList");
decisionList.addEventListener("change", function () {
var finalChoice = document.getElementById("choice");
finalChoice.innerHTML='<input id="finalDate" type="date">'
}
and other event referring to 'id' from innerHTML:
var payment = document.getElementById("finalDate");
payment.addEventListener("change", function () {
var textDate = payment.textContent;
alert(textDate);
})
The above is not working. Is it possible or not?
It is possible, but make that payment getter lazy. What that means is, instead of setting up that second change listener right away (in your other code), make that other code a function. Then in your first trigger, where you created the extra div or input or something, call that setup function.
decisionList.addEventListener("change", function () {
const finalChoice = document.getElementById("choice");
finalChoice.innerHTML='<input id="finalDate" type="date">'
createFinalDateListener();
}
function createFinalDateListener() {
const payment = document.getElementById("finalDate");
payment.addEventListener("change", function () {
const textDate = payment.textContent;
alert(textDate);
});
}
Here's a similar example. I do not have the input immediately. Or listener. And I only create a listener after I create the input.
// Here's the main trigger
function addExtraElements() {
// let's create a datepicker dynamically.
document.querySelector('#placeholder').innerHTML = '<input type="date" placeholder="pick date">';
listenDateChanges();
// TODO: don't forget to add cleanup code! Each time you fill that innerHTML, the old listener will remain
}
// Here's your datepicker listener
function listenDateChanges() {
const datePickerEl = document.querySelector('input[type="date"]');
if (!datePickerEl) {
console.log('no picker');
return;
}
datePickerEl.addEventListener('change', () => alert(datePickerEl.value));
}
<div id="placeholder">
Placeholder
</div>
<button onclick="addExtraElements()">Add extra elements</button>
I'm trying to understand if there is a way to forward DOM event (form's one) to parent after calling a function internally.
REPL: https://svelte.dev/repl/8eb540552faa4651a398b182fa5cdd48?version=3.24.1
As you can see I call validate() in Form.svelte with <form on:submit|preventDefault={validate}>.
After that I need to re-dispatch event / call the handleOnSubmit() in App.svelte, the parent.
Is there a way without using createEventDispatcher?
How about defining the <form> element as an export which you can then use in its parent and define a onsubmit eventlistener? You can use export let form; and bind:this={form} on the <form>.
In Form.svelte:
<script>
export let form;
function validate() {
console.log("I'm the validate() function")
}
</script>
<form bind:this={form} on:submit|preventDefault={validate}>
<input>
<button type="submit">
Please save me
</button>
</form>
In App.svelte:
<script>
import Form from "./Form.svelte"
import {onMount} from "svelte"
let form;
onMount(()=> {
form.onsubmit = handleOnSubmit
})
function handleOnSubmit() {
console.log("I'm the handleOnSubmit() in App.svelte")
}
</script>
<Form bind:form={form} on:submit={handleOnSubmit}>
</Form>
I am trying to call method on pressing enter key but it's not working. Code is as below.
<template>
<div>
<button #click="callEvent" #keyup.enter="callEvent"> Click </button>
</div>
</template>
<script>
export default{
methods:{
callEvent(){
console.log("Event called");
}
}
}
</script>
The click event already triggers with the ENTER key (it also triggers with Space in some browsers, like Chrome for desktop). So, your code only needs a #click="callEvent" and everything works well since the focus is already on the button:
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<button #click="callEvent">Enter</button>
</div>
If you want that any ENTER triggers the button even if it isn't with focus, you should bind the event to the window object, which can be made inside the mounted handler:
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
},
mounted() {
window.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
app.callEvent();
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<button>Enter</button>
</div>
Remember that if you're using Single File Components, the instance is exposed by the this keyword, which can be used to call component methods inside the desired handler:
export default {
methods: {
callEvent() {
console.log('Event called')
}
},
mounted() {
window.addEventListener('keyup', event => {
if (event.keyCode === 13) {
this.callEvent()
}
})
}
}
Buttons don't have keyup event on them. Even when you have focus on the button, and hit enter, it will be considered a click event, instead of keyup.enter.
Try binding the event to an input and it'd work.
Alternatively, you could use jQuery (or Plain JS) to bind for keydown event on the body element, and trigger the Vue method by calling app.callEvent().
var app = new Vue({
el: "#app",
methods: {
callEvent() {
console.log("Event called");
}
},
mounted() {
var self = this;
window.addEventListener('keyup', function(event) {
if (event.keyCode === 13) {
self.callEvent();
}
});
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.2/vue.min.js"></script>
<div id="app">
<template>
<div>
<button #click="callEvent"> Click </button>
</div>
<input type="text" #keyup.enter="callEvent" />
</template>
</div>
Updated to use mounted instead of relying on jQuery - as per Erick Petrucelli's answer as it allows referring to the Vue component without the global variable.
I experienced inconsistent results when using native JS with window.addEventListener. VueJS natively supports modifying behavior for keyboard events https://v2.vuejs.org/v2/guide/events.html#Key-Modifiers
This also worked a lot better in my case due to needing separate behavior for the tab key.
Your input can look like this with custom modifiers on each key up|down
<input
type="text"
class="form-control"
placeholder="Start typing to search..."
v-model="search_text"
#focus="searchFocus"
#blur="searchFocusOut"
v-on:keyup.enter="nextItem"
v-on:keyup.arrow-down="nextItem"
v-on:keyup.arrow-up="nextItem"
v-on:keydown.tab="nextItem"
>
Then inside NextItem you can reference the event, and get each key.. or write a separate function for each key modifier.
#keyup.enter="callEvent"
change to
#keypress.enter.prevent="callEvent"
<template>
<div>
<button #click="callEvent" #keypress.enter.prevent="callEvent"> Click </button>
</div>
</template>
Ref: https://github.com/vuejs/vue/issues/5171