i have two components sdk-button and an upper-component
in my sdk-button i have the following simple code:
public render() {
return html`
<button #click=${this._handleClick}>${this.text}</button>
`;
}
_handleClick() {
let click = new Event('click');
this.dispatchEvent(click);
}
This dispatches a click event so in my upper-component i have the following code:
public render() {
return html`
<h1>Hello, ${this.test}!</h1>
<sdk-button #click="${() => { this.changeProperty() }}" text="Click"></sdk-button>
`;
}
changeProperty() {
let randomString = Math.floor(Math.random() * 100).toString();
console.log(randomString)
}
This works however the changeProperty is fired twice. can anyone tell me why this is happening?
I am pretty sure that's because there are two click events: the native one from the button which bubbles up, and the one you dispatch manually. Try to either use a custom event with a different name or remove the dispatch from the sdk-button and use the native one.
Related
A little confused on why the below isn't working. I have tried this where I don't pass an argument to the function and can get the UI to update but if I pass the argument in and do the same exact stuff to it it updates it in the code and I can console.log it out but the UI is not updated.
This Works:
<script>
import { text, toggle_class } from "svelte/internal";
let line1 = {name:"line 1", display:"some text here", originalText:"some init text", visible:true};
function toggleView(){
line1.visible = !line1.visible;
if(!line1.visible) line1.display = "*************"
else line1.display = line1.originalText
};
</script>
<main>
<button on:click="{toggleView}">{line1.display}</button>
</main>
This does NOT work:
<script>
import { text, toggle_class } from "svelte/internal";
let line1 = {name:"line 1", display:"some text here", originalText:"some init text", visible:true};
function toggleView(field){
field.visible = !field.visible;
if(!field.visible) field.display = "*************"
else field.display = field.originalText
};
</script>
<main>
<button on:click="{toggleView(line1)}">{line1.display}</button>
</main>
I think it might be because I'm assigning this to a local variable from Svelte's point of view but I'm not sure how else I would call a function to make this reusable since I would be doing this for a bunch of lines.
Any help would be appreciated.
UPDATE - Solved
Below works based on Thomas' answer below:
<script>
import { text, toggle_class } from "svelte/internal";
var lines = {
line1: {
name: "line 1",
display:"some text here",
originalText:"some init text",
visible:true
}
};
function toggleView(field) {
return function() {
lines[field].visible = !lines[field].visible;
if (!lines[field].visible) {
lines[field].display = "*************";
} else {
lines[field].display = lines[field].originalText;
}
}
}
</script>
<main>
<button on:click="{toggleView('line1')}">{lines.line1.display}</button>
</main>
When you set on:click to toggleView(line1), the function is executed right away, just as if you'd set it to toggleView() instead of toggleView in your other, working example.
You have two ways to fix this. The most common approach is to turn your on:click handler into a lambda/anonymous function:
<button on:click="{() => toggleView(line1)}">{line1.display}</button>
The other, less common approach is to modify your handler function and turn it into a curried function:
function toggleView(field) {
return function() {
field.visible = !field.visible;
if(!field.visible) field.display = "*************"
else field.display = field.originalText
};
};
in which case on:click={toggleView(field)} will return a function that will be bound to the click handler, and that function will actually execute when the button is clicked.
In raw HTML + JS, an inline event handler like onclick is actually a bit of code that will be executed when the event happens. In particular, if you use a function in your handler, you must call it:
Example (not Svelte):
<button onclick="toggleView()" />
In contrast, in Svelte, a event handler is expected to be a function, that will be called when the event happens.
OK because toggleView is a function:
<button on:click={toggleView} />
Not OK because it calls toggleView() every time the markup is rendered (i.e. when reactive vars change), and _the return value of togggleView() is bound to the click event:
<button on:click={toggleView()} />
If you need to pass local arguments to an event handler, then you need to wrap the function to still get what you want (that the whole is called when the event happens, not when the markup is rerenred):
Example, OK + argument:
<button on:click={() => toggleView(line1)} />
Note that here, the handler is () => toggleView(line1), not toggleView(line1).
I am looking for a way to listen to a dispatched event from a Svelte component within another component from JavaScript (and not from the on: syntax).
Here is the code I am trying to achieve on REPL.
The expected behaviour would be to have 0 displayed in the console when the button Close 0 is clicked, and so on for the other ones.
I went through some digging in svelte's compiled code, and I found a solution to listen on svelte's handled event, but it's not pretty :)
You can (and should) dispatch your own custom event when calling onClose, but here's the solution:
on Nested.svelte
<script context="module">
let counter = 0
</script>
<script>
import { createEventDispatcher, onMount } from 'svelte';
// add this
import { get_current_component } from 'svelte/internal';
let _this;
const id = counter++
const dispatch = createEventDispatcher()
/*********
and add this reactive statement
**********/
$: {
if (_this){
_this.parentNode.hosts = (_this.parentNode.hosts || []);
_this.parentNode.hosts.push(get_current_component());
}
}
/*********
end
**********/
function onClose() {
dispatch('close', id)
}
</script>
<!-- bind this -->
<button bind:this={_this} class='nested-button' on:click={onClose}>
Close {id}
</button>
Then in your App.svelte
<script>
import { onMount } from 'svelte'
import Nested from './Nested.svelte'
let element
onMount(() => {
// requestAnimationFrame is required!
requestAnimationFrame(() => element.hosts.forEach(nestedButton => {
nestedButton.$on('close', (e) => {
console.log(e.detail)
})
}));
})
</script>
<ul bind:this={element}>
<Nested/>
<Nested />
<Nested />
</ul>
Explanation -
the only way to bind to a svelte event is by getting a reference to the calling component and calling the $on method, but currently there's no easy way of getting a component's reference.
so what I did was calling the svelte's internal get_current_component, which will return the current component (but for some reason won't work when called inside onMount).
I appended the reference to the parentNode.hosts of the top most element in the component, which in this case is the ul element.
then you can just call the $on method for each reference in element.hosts.
The appropriate solution however will be dispatching you own event like this:
function onClose() {
dispatch('close', id)
this.dispatchEvent(new CustomEvent('close', {detail: id}));
}
And by that achieving almost the exact same thing without messing with svelte's internals
I make use of svelte stores and reactivity:
signals.js:
import { writable } from 'svelte/store';
export const endSignal = writable({});
Sender.svelte:
<script>
import { endSignal } from './signals.js';
const signal = $endSignal;
function handleEndSignal(){
// do other stuff, then send signal
endSignal.update(()=> signal);
}
</script>
<button on:click={handleEndSignal}>The End</button>
Receiver.svelte:
<script>
import { endSignal } from './signals.js';
$: endItAll(), $endSignal;
let countEnds = 0;
function endItAll(){
countEnds +=1;
}
</script>
<p>times end signal received: {countEnds}</p>
Basically, every time we click the button in Sender.svelte, the value "endSignal" in "signals.js" is overwritten, hence in Receiver.svelte the updated variable in the "$:" statement triggers the function "endItAll()".
TL;DR; Is it possible to define an event listener in HTML (not JS) for a custom events?
Basing on this codepen, I'm trying to do the following:
<my-checkreport
onclick="myFunction()"
oncheck="myFunction1)"
check="myFunction()"
></my-checkreport>
where myFunction does some console.log stuff that I can see in the browser console. Native onlick works, of course, but neither oncheck nor check work, for the custom event defined below (source taken from above linked codepen):
customElements.define('my-checkbox', class extends HTMLElement {
constructor() {
super();
const shadowRoot = this.attachShadow({mode: 'open'});
this.checkEvent = new CustomEvent("check", {
bubbles: true,
cancelable: false,
});
shadowRoot.innerHTML = `
<label>
<input type="checkbox" id="my-checkbox"></input>
Dummy Enabled
</label>`;
shadowRoot.querySelector('#my-checkbox').addEventListener('click', (e) => {
console.log('checked', e.target.checked);
this.dispatchEvent(this.checkEvent);
});
}
});
Is it possible to attach event listeners for custom events in HTML?
This is the closest I can come to emulating the DOM declaration of an event handler. This code does everything, from what I can tell, that the DOM does for built in event handlers.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>On-Event Test</title>
<script>
function onCheckHandler(event) {
console.log('onCheckHandler: %O', event);
event.stopPropagation();
event.stopImmediatePropagation();
//event.preventDefault();
}
function eventListener(event) {
console.log('eventListener: %O', event);
}
(function() {
var template = `<div>Click to Check</div>`;
class MyEl extends HTMLElement {
constructor() {
super();
var rootEl = this.attachShadow({mode: 'open'});
rootEl.innerHTML = template;
rootEl.firstChild.addEventListener('click', () => {
var checkEvent = new CustomEvent("check", {bubbles:true,cancelable:true});
if (this.dispatchEvent(checkEvent)) {
// Do default operation here
console.log('Performing default operation');
}
});
this._onCheckFn = null;
}
static get observedAttributes() {
return ['oncheck'];
}
attributeChangedCallback(attrName, oldVal, newVal) {
if (attrName === 'oncheck' && oldVal !== newVal) {
if (newVal === null) {
this.oncheck = null;
}
else {
this.oncheck = Function(`return function oncheck(event) {\n\t${newVal};\n};`)();
}
}
}
get oncheck() {
return this._onCheckFn;
}
set oncheck(handler) {
if (this._onCheckFn) {
this.removeEventListener('check', this._onCheckFn);
this._onCheckFn = null;
}
if (typeof handler === 'function') {
this._onCheckFn = handler;
this.addEventListener('check', this._onCheckFn);
}
}
}
// Define our web component
customElements.define('my-el', MyEl);
})();
</script>
</head>
<body>
<my-el oncheck="onCheckHandler(event)"></my-el>
</body>
</html>
To get this to work you need two steps:
Step One:
The component code must support an attribute change.
When the attribute is set to a string (The function to call) then the code creates a temporary function that attempts to call the function provided as the attribute value. That function is then passed on to step two.
When the attribute is set to anything else or the attribute is removed then the code passes a null on to step two.
Step Two:
The component code must support the oncheck property.
Whenever this property is changed it needs to remove any previously defined event handler for this property by calling removeEventListener.
If the new value of the property is a function then is calls addEventListener with that function as the handler.
This was fun to figure out. At first I thought that the DOM based oncheck handler would always be first. But by testing onclick and addEventHandler('click') I discovered that the event handlers are added in the order received. If onclick is provided in the DOM than that event listener is added first. If you then call removeAttribute('onclick') and then setAttribute('onclick', 'handler(event)') then that event handler is moved to the end of the list.
So this code replicates exactly what I same with the click event.
Without any JS code? No. But you could define a property in the constructor of your element to get the element attribute and eval it. For example,
constructor() {
// Other code.
const onCheckFunc = this.getAttribute('oncheck')
this.addEventListener('check', () => eval(onCheckFunc))
// Rest of your code.
}
EDIT: As mentioned by #Intervalia, you shouldn't inspect attributes of the element in constructor. You could do this in connectedCallback. But remember:
In general, work should be deferred to connectedCallback as much as possible—especially work involving fetching resources or rendering. However, note that connectedCallback can be called more than once, so any initialization work that is truly one-time will need a guard to prevent it from running twice.
In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.
So what i'm basically trying to do is simple
class Something extends React.Component {
validateEmail () {
//code that validates email,innerHTML a div.status element if error occurs
this.removeStatus();//then remove status onkeydown of input element
}
removeStatus () {
//code that removes the status onkeydown of input element
}
}
for some reason it's not working. in my javascript console (chrome)
i'm getting this
login.js:132Uncaught TypeError: this.removeStatus is not a function
Edit 1: I've added the actual code, as you can see i'm binding validateEmail in the constructor
class Email extends React.Component {
constructor(props) {
super(props);
this.change = this.change.bind(this);
this.validateEmail = this.validateEmail.bind(this);
this.state = {
value : ''
}
}
removeStatus() {
$('input').on('keydown',function () {
$('.contextual-info').fadeOut();
});
}
validateEmail(event) {
event.preventDefault();
var token = $('#token').val();
var email_regex=/^([\w-]+(?:\.[\w-]+)*)#((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$/i;
if ($.trim(this.state.value) !== "") {
if (email_regex.test(this.state.value)) {
$.ajax({
url:'/login',
type:'post',
data:{email:this.state.value,_token:token},
success: function (response) {
if (response) {
$('#email').remove();
$('.btn').remove();
$('#status').html('');
ReactDOM.render(<Password /> ,document.getElementById('login-dialogue'));
$('input[type="password"]').focus();
} else {
$('input#email').addClass('input-context');
if($('#status').html('<div class="bg-danger contextual-info wrong">Email Address Not Found!</p>')){
this.removeStatus();
}
}
}
});
} else {
if($('#status').html('<div class="bg-danger contextual-info wrong">Invalid Email Address</div>')){
this.removeStatus();
}
}
} else {
if($('#status').html('<div class="bg-danger contextual-info wrong">Can\'t submit an empty field!</div>')){
this.removeStatus();
}
}
}
change (event) {
this.setState({
value : event.target.value
});
}
render(){
return(
<div className="login-dialogue" id="login-dialogue">
<h1 className="text-center">Log in</h1>
<div id="status"></div>
<form action="" onSubmit={this.validateEmail} id="validateEmail">
<input type="email" id="email" value={this.state.value} placeholder="Email Address" onChange={this.change} />
<button type="submit" className="btn btn-flat btn-wide teal white-text">Continue</button>
</form>
</div>
);
}
}
ReactDOM.render(<Email /> ,document.getElementById('flex-me'));
Your methods are defined properly, so the problem is in how you call validateEmail.
You're invoking it in a way that sets this to something other than your Something instance. This is common in event listeners. I guess you have some code like this in your render:
<button onClick={this.validateEmail} />
The recommended solution for React is to bind event handlers in your constructor:
class Something extends React.Component {
constructor() {
super();
this.validateEmail = this.validateEmail.bind(this);
}
// ...
}
You could also call the method from inside an arrow function, which preserves the value of this at the place it was declared:
<button onClick={() => this.validateEmail()} />
The disadvantage of this approach is that a new onClick handler is created each time your component is rendered.
EDIT: same problem, different place. You call removeStatus inside a function, which loses the outer this binding. Use an arrow function instead:
$.ajax({
success: (response) => {
// etc
this.removeStatus();
}
})
Further reading:
this on MDN
React "reusable components" docs
I had a similiar Problem
I just ran into a similar problem with the same error message. What is weird is that sometimes when i call a method inside another method it works. I am making a Simon game and the following method in a method works.
handleMouseDown (e) {
if(e.target.id==="green"){
$("#green").addClass("colorGreenPush");
greenSound.play();
clicked.push(1);
this.handleCheck();
}
I am simply running a check method when the user presses a button. No error.
However I was getting a TypeError with this code
handleCheck () {
var display = $("#display");
if(litID.length == clicked.length){
if(litID.join() == clicked.join()){
if(round == 20){
setTimeout(function(){
alert("Unreal. You Acually Won!!!");
location.reload();
},1000);
}
else{
setTimeout(function(){
display.text("-"+(round + 1)+"-");
round ++;
console.log(this);
litID = [];
clicked = [];
j = 0;
this.handleGame();
},1000);
}
}
Near the end I try this.handleGame(); and it wasn't working.
My Solution
You will notice I did a console.log(this); just to see what this was at this point. Turns out this was the Window Object. I don't know why, perhaps being buried in if/else statements. Both methods are binded so that wasn't the problem.
Anyhow. I went poking around in the window object and turns out that i can use this.App.prototype.handleGame(); and it works! I should add that the name of my main ES6 Component is App. This will vary based on your component and method names, but the overall principle should still apply.
I Am In Shock
I'll admit I am a newb. This is my second mini project in React but finding this was the coolest thing. Now there may be other ways like the arrow functions mentioned above, but this is what worked for me. And it was the difference between having a game and having straight garbage. Hope this helps someone.
I am working with React and I am doing a chat app. As you now, everytime there is a new message or any event in the chat screen, that last event/item/message should be focus so the user doesn't have to scroll down in order to look for the new event in the chat. As a regular chat app.
Every event is attached to this.props.chatMessages.
I already accomplished this behavior but only when the user adds a new message. And I need that functionality for everything, sometimes the chat says
New user was added to the chat
or
User XXXXX leaved the chat
so, those are different events, those are informative messages and not regular user messages.
As I mentioned before, every new event is attached to this.props.chatMessages
this is what I did in order to focus in the last message when the user sends a message by himself
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
and in the render method I have something like this
chatForm = <ChatForm onAddMessage={this.addMessage} />;
here is the full function just in case. Where you see <ChatItem .../> is because that is the component to visualize every new event happening in the chat.
class ChatView extends React.Component {
static getStores () {
return [ ChatStore ];
}
static getPropsFromStores () {
return ChatStore.getState();
}
constructor (props) {
super(props);
this.addMessage = this.addMessage.bind(this);
this.focusOnLastMessage = this.focusOnLastMessage.bind(this);
ChatActions.connect({ socketUrl : this.props.socket, mode : this.props.mode, room : this.props.room, user : this.props.user });
}
addMessage (text) {
if (text.length) {
ChatActions.addMessage(text);
this.focusOnLastMessage();
}
}
focusOnLastMessage () {
console.log(this.props.chatMessages);
let lastMessage = React.findDOMNode(this.refs.messages);
lastMessage.scrollTop = lastMessage.scrollHeight;
}
render () {
let messages = this.props.chatMessages.map((message) => {
return <ChatItem info={message.info} me={message.me} player={message.player} message={message.message}
onNewEvent={this.focusOnLastMessage} />;
}), chatForm, hr, dealerPlayerMessages, dealerPlayerBox, minusPlusButtons;
if (this.props.mode === 'player') {
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
hr = <hr />;
chatForm = <ChatForm onAddMessage={this.addMessage} />;
dealerPlayerBox = <div>{dealerPlayerMessages}{hr}{chatForm}</div>
}
if (this.props.mode === 'dealer') {
minusPlusButtons = <MinusPlusButtons />
dealerPlayerMessages = <ul ref="messages">{messages}</ul>;
dealerPlayerBox = <div> {minusPlusButtons} {dealerPlayerMessages}</div>
}
return <div>
{dealerPlayerBox}
</div>;
}
}
so, what should I do in order to listen to every change in this.props.chatMessages in order to focus on every new item in the chat ?
I'm just guessing here, but let's say a new person joins the chat updating this.props.chatMessages to contain a new message notifying users about this change. This means that the first lifecycle method is going to fire
componentWillReceiveProps (nextProps) {
// do something with new message
}
But you need to scroll the message after this has been painted to the dom, so luckily there's a life cycle method for that too.
componentDidUpdate (prevProps, prevState) {
// dom has been updated with new message, scroll your screen!
this.focusOnLastMessage()
}
Edit: You may need to bind this in your constructor to use this, but I don't remember. Not all lifecycle methods need it.
Lifecycle Methods in docs