How to trigger a vue method only once, not every time - javascript

I'm handling a rotate even on change:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
}
}
}
</script>
Right now, the second this.rotate runs on every change. How can I do it so that the second this.rotate is applied only the first time handleRotate runs?

Solving it Vue way:
You can use $once, which will listen for a event but only once.
Listen for a custom event, but only once. The listener will be removed once it triggers for the first time.
You just need to add .once to #change like following:
<div #change.once="handleRotate"></div>
<script>
export default {
//No Change here
}
</script>
Check demo if this in the fiddle.
Old Answer:
If you do not want to have initial value set for rotate, you can have one more variable : hasRotated to track whether rotate has been changed or not. Initially set hasRotated to true, once rotate has been changed set hasRotated to false, like following:
<div #change="handleRotate"></div>
<script>
export default {
data: {
rotate: 123,
hasRotated: false
},
methods: {
handleRotate () {
if(this.hasRotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY)
this.hasRotated = false
}
}
}
}
</script>

one simple solution would be to add a marker somewhat like this:
<script>
export default {
data: {
rotate = 0
},
methods: {
handleRotate () {
if(!this.rotated){
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
this.rotated = true;
}
}
}
}
</script>
of course you would need to initiate this.rotated as false

If rotate start always at zero you can do:
export default {
data: {
rotate = 0
},
methods: {
handleRotate(e) {
if (this.rotate !== 0) {
return;
}
this.rotate = this.rotate + this.getRotateAngle(e.clientX, e.clientY);
}
}
};

Related

Class method breaks after resize event listener

Here is a small example of my problem. I am trying to build a responsive class that will adjust my page when I change the width size.
Upon changing the size the method that was originally called disappears. You can test it yourself by adjusting the page size.
class PageManager {
constructor() {
this.width;
this.isMobile;
}
init() {
this.attachListeners();
this.widthChanges();
this.adjustPage();
}
attachListeners() {
window.addEventListener("resize", this.widthChanges);
}
widthChanges() {
this.width = window.innerWidth;
this.adjustPage();
if (this.width < 491) {
this.isMobile = true
} else {
this.isMobile = false
}
}
adjustPage() {
console.log("fired");
}
}
const pageManager = new PageManager();
pageManager.init();
You're having a scope problem, the function adjustPage doesn't exists under the scope of the function widthChanges.
When you call this.adjustPage() in the function widthChanges, this will be the context of the function widthChanges not the context of the class PageManager, so, the function ajustPage doesn't exist.
You can fix your problem binding the class context into the functions, just like bellow.
You can find a little bit more about this and scrope here : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this
class PageManager {
constructor() {
this.width;
this.isMobile;
this.attachListeners = this.attachListeners.bind(this);
this.widthChanges = this.widthChanges.bind(this);
}
init() {
this.attachListeners();
this.widthChanges();
this.adjustPage();
}
attachListeners() {
window.addEventListener("resize", this.widthChanges);
}
widthChanges() {
this.width = window.innerWidth;
this.adjustPage();
if (this.width < 491) {
this.isMobile = true
} else {
this.isMobile = false
}
}
adjustPage() {
console.log("fired");
}
}
const pageManager = new PageManager();
pageManager.init();
The callback to addEventListener is called with the object on which the event was added (in this case, the window) bound as this. You can either use an arrow function or bind the this value yourself.
window.addEventListener("resize", this.widthChanges.bind(this));
//or
window.addEventListener("resize", ()=>this.widthChanges());

Vue Transition - JavaScript hooks

Based on this answer, I'm trying to create a Vue slideToggle component using transition.
The slideToggle is a classic paradigm in height animation. I've been successful so far...
I don't want to set a fixed max-height or height, I want the height to be dynamic.
My animation is working properly when displaying and hiding. The problem is in canceling while displaying or hiding.
How to handle the #enter-cancelled and the #leave-cancelled hooks? I'm new to vue transitions :)
I put my code inside this CodeSandBox: https://codesandbox.io/s/vue-template-3b7oj
I don't know if this helps you, but try this:
declare a new variable:
data() {
return {
height: null,
toggling: false
};
},
when the open or close function start, verify if toggling is true, if yes, just cancel, like this:
enter(el) {
if (this.toggling) return;
this.toggling = true;
this.height = el.offsetHeight;
el.style.overflow = "hidden";
el.style.height = 0;
el.style.paddingTop = 0;
el.style.paddingBottom = 0;
el.style.marginTop = 0;
el.style.marginBottom = 0;
setTimeout(() => {
el.style.transitionProperty = `height, margin, padding`;
el.style.transitionDuration = this.duration + "ms";
el.style.height = this.height + "px";
el.style.removeProperty("padding-top");
el.style.removeProperty("padding-bottom");
el.style.removeProperty("margin-top");
el.style.removeProperty("margin-bottom");
this.toggling = false;
});
},
Will be something like this:
https://codesandbox.io/s/vue-template-78n7t?fontsize=14
Maybe i broke your code, sorry, but will you get the idea.
As per the offical documentation Javacript transition hooks
the #leave-cancelled is only available with v-show, where are in your sample code you are using v-if, if you change this you will be able to capture the #leave-cancelled hook,#leave-cancelled and #enter-cancelled are triggered when enter or leave are interrupted, say you press the toggle button while opening as well as pressing the button while its closing.
Vue-Transition-Cancel
tl;dr
leave event cancels not yet called enter
enter cancels not yet called leave
cancel state is stored in
el._enterCb.cancelled
el._leaveCb.cancelled
analysis
Consider:
const cb = el._enterCb = once(() => {
if (expectsCSS) {
removeTransitionClass(el, toClass)
removeTransitionClass(el, activeClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass)
}
enterCancelledHook && enterCancelledHook(el)
} else {
afterEnterHook && afterEnterHook(el)
}
el._enterCb = null
})
source: _enterCb
So a naive solution to cancel #enter is
el => {el._enterCb.cancelled = true; done()}
This is what actually happens when one triggers leave
// call enter callback now
if (isDef(el._enterCb)) {
el._enterCb.cancelled = true
el._enterCb()
}
source: leave
Same applies to:
const cb = el._leaveCb = once(() => {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null
}
if (expectsCSS) {
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass)
}
leaveCancelled && leaveCancelled(el)
} else {
rm()
afterLeave && afterLeave(el)
}
el._leaveCb = null
})
source: _leaveCb
One can check for possible assignments:
https://github.com/vuejs/vue/search?q=_leaveCb&unscoped_q=_leaveCb

How to render a self-destructing paragraph at mouse position in React?

I am trying to get a paragraph to appear at the location of the mouse coordinates, but self-destruct after 1 second.
$(function(){
var fadeDelay = 1000;
var fadeDuration = 1000;
$(document).click(function(e){
var div = $('<div class="image-wrapper">')
.css({
"left": e.pageX + 'px',
"top": e.pageY + 'px'
})
.append($('<img src="" alt="myimage" />'))
.appendTo(document.body);
setTimeout(function() {
div.addClass('fade-out');
setTimeout(function() { div.remove(); }, fadeDuration);
}, fadeDelay);
});
});
The code above is from a fiddle which represents the effect that I am looking for; however, it uses jQuery - while I am working with React.
I tried following this linear process:
1 - In the state, toggle a boolean with mouse clicks
playerAttack = () => {
this.setState({ hasPlayerAttacked: true })
}
2 - In a function, when the boolean is true, return a paragraph and set the boolean back to false
renderDamageDealtParagraph = () => {
if (this.state.hasPlayerAttacked) {
return <p>{this.state.playerAttack}</p>;
this.setState({ hasPlayerAttacked: false });
}
};
However, with this approach there were too many fallacies; main one being that upon resetting the boolean back to false, the rendered paragraph immediately disappears (instead of after a timeout of 1000ms).
What is the best wait to implement something like the linked fiddle, in ReactJS using vanilla JS?
Thanks in advance to whoever might be able to help.
You can basically do something like this:
Have state to track the mouse position x and y, and two booleans isShown and shouldHide to coordinate the disappering div
On click, show the div by setting isShown to true and immediately setTimeout to start hiding it in the future by adding a class by flipping the shouldHide to true
Once the class is added, the element will fade and will trigger the transitionend event at which point you can remove the div entirely by flipping the isShown to false and shouldHide to false boolean
Sample Implementation (Sorry for the shitty code, been a while since I React-ed)
JS Fiddle
class SelfDestructDemo extends React.Component {
constructor(props) {
super(props);
this.state = {
x: 0,
y: 0,
isShown: false,
shouldHide: false
};
this.handleClick = this.handleClick.bind(this);
this.reset = this.reset.bind(this);
}
reset() {
this.setState({
x: 0,
y: 0,
isShown: false,
shouldHide: false
});
}
handleClick(event) {
if (this.state.isShown) {
return;
}
const { clientX, clientY } = event;
this.setState({
x: clientX,
y: clientY,
isShown: true,
shouldHide: false
});
setTimeout(() => {
this.setState({ shouldHide: true });
}, 1000);
}
render() {
const p = this.state.isShown && (
<div
onTransitionEnd={this.reset}
className={`${this.state.shouldHide ? "should-hide" : ""} modal`}
style={{ top: this.state.y, left: this.state.x }}
>
Self destructing....
</div>
);
return (
<div className="container" onClick={this.handleClick}>
{p}
</div>
);
}
}
ReactDOM.render(<SelfDestructDemo />, document.querySelector("#app"));

Vuejs - On input, run a function (but with a delay)

I have an input field, and v-on:input it runs a method called activate that looks like this:
export default: {
data() {
return {
isHidden: true
}
},
methods: {
activate() {
this.isHidden = false;
}
}
}
isHidden turns on/off some icon (it doesn't really matter what this data property is; I'm just using it for example purposes).
So currently, when a user does an input it immediately turns on the activate function. Is there a way to, perhaps, put it on a delay via setTimeout? I've tried doing the following but it doesn't work:
methods: {
setTimeout(function() {
activate() {
this.isHidden = false;
}
}, 500)
}
Try this:
methods: {
activate() {
setTimeout(() => this.isHidden = false, 500);
}
}
Or without arrow function:
methods: {
activate() {
var that = this;
setTimeout(function() { that.isHidden = false; }, 500);
}
}
First, set a var in your data:
data() {
return {
typing: Date.now()
}
}
then in your methods, create a function that will fire on keyup:
pendingSave(val){
let saving = setTimeout(() => {
this.saveItem(val) // method to call when user is done typing
},1203)
if(val){
saving
if(Date.now() - this.typing < 1200) clearTimeout(saving)
this.typing = Date.now();
}
}
In your HTML, you would have something like this:
<input v-model="title" v-on:keyup="pendingSave(title)" type="text" placeholder="Title" />
What happens is that when the user clicks inside the input, and types a character, it will call 'pendingSave()'. This will get the function ready to call via setTimeout() in 1203ms. If the user types another character, and the time is 'less' than 1200ms, it will cancel setTimeout()... otherwise it will fire the function saveItem() as intended.

React - Button Pressed, keep calling function

I'm trying to implement a zoom function. onClick works fine, but I'd like to have it when I hold the zoom button down, it zooms continuously. How can I implement this with ReactJS?
Jquery: mousedown effect (while left click is held down)
I was using this as a template, but onMousedown doesn't get registered according to console.log
<div className="zoomControl" >
<button className="zoomIn" onMouseDown={this.zoomIn}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
zoomIn = () => {
console.log('test');
var self = this;
this.timeout = setInterval(function(){
// Do something continuously
this.renderer.zoomIn();
}, 100);
return false;
};
zoomMouseUp = () => {
clearInterval(this.timeout);
return false;
};
You need to use both mouseUp and mouseDown. Start a time on mouseDown and call the zoom function with the timeout repeatedly and clear the time on mouseUp.
Here a demo with zoomIn and zoomOut to compare and better understand the algorithm.
Hope this helps!!
class Zoom extends React.Component {
constructor(props) {
super(props)
this.state = {
zoom: 1,
}
this.t = undefined
this.start = 100
this.repeat = this.repeat.bind(this)
this.onMouseDown = this.onMouseDown.bind(this)
this.onMouseUp = this.onMouseUp.bind(this)
this.zoom = this.zoom.bind(this)
this.zoomOut = this.zoomOut.bind(this)
}
zoom(){
this.setState({zoom: this.state.zoom + 0.1})
}
repeat() {
this.zoom()
this.t = setTimeout(this.repeat, this.start)
this.start = this.start / 2
}
onMouseDown() {
this.repeat()
}
onMouseUp() {
clearTimeout(this.t)
this.start = 100
}
zoomOut(){
this.setState({
zoom: 1
})
}
render() {
return <div className="zoomControl" >
<div className="zoom" style={{transform: 'scale('+ this.state.zoom +')'}}></div>
<button className="zoomIn" onMouseUp={this.onMouseUp} onMouseDown={this.onMouseDown}>+</button>
<button className="zoomOut" onClick={this.zoomOut}>-</button>
</div>
}
}
ReactDOM.render(<Zoom/>, document.getElementById('app'))
body {
overflow: hidden
}
.zoom {
width: 20px;
height: 20px;
margin: 0 auto;
background: red;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
If you have to do some kind of animation here, you're better off using requestAnimationFrame than setting intervals. I'd do it something like this.
class App extends React.Component {
state = {
value: 0,
mousedown: false
}
zoom = () => {
if (this.state.mousedown) {
this.setState({ value: this.state.value + 1},
() => { window.requestAnimationFrame(this.zoom) }
)
}
}
zoomIn = () => {
window.requestAnimationFrame(this.zoom);
}
toggleMouseDown = () => {
this.setState({
mousedown: !this.state.mousedown
});
this.zoomIn()
}
render() {
return(
<div>
<button
onMouseUp={this.toggleMouseDown}
onMouseDown={this.toggleMouseDown}>
Click me
</button>
{/* The rest of your component goes here */}
</div>
);
}
}
It's hard to get all of the context, but I'll try to give a relevant answer:
You don't have any property set to call zoomMouseUp when you release the button. I'd start with:
<button className="zoomIn" onMouseDown={this.zoomIn} onMouseUp={this.zoomMouseUp} onMouseOut={this.zoomMouseUp}>+</button>
You stated that it starts zooming, but doesn't stop. That makes me assume it's working, so that should probably fix it. I added the onMouseOut because if they press the button and move the mouse away without releasing it, it's going to continue.
There are a lot of ways to do this, but that's probably the most simple with what you have.
My issue was due to right click being the primary click or some thing along the lines. It works fine as is.

Categories

Resources