How to invoke a function at a trigger point? - scrollmagic

When a trigger point is reached, I just want to call a function or run a few statements to do something, which has nothing to do with interface. Here is how I am doing now for this purpose:
var scene = new ScrollMagic.Scene({triggerElement: "#some-trigger", triggerHook: 'onLeave', offset: })
.on('start', function () {
// statements go here
}).addTo(controller);
Is this the correct way?
The reason to ask this question is that I find out Scroll Magic can be a UNIQUE event control tool.

Little later but I jump to that question because I had the same issue in some projects.
So i solved in two ways:
Using enter or progress event
ENTER is trigger when a scene is starting. It's depends on triggerHook
scene.on("enter", function (event) {
console.log("Scene entered.");
});
PROGRESS is trigger every time
scene.on("progress", function (ev) {
if( ev.progress > 0.25){
//== going farward
}
if(ev.progress < 0.25){
//== going backward
}
})

Related

React - use Dependencies in useEffect without trigger the effect itself when the Dependency change

I have a react state
const [list, setList] = useState([])
and a react effect that is triggered when the list is modified, and do some work with the first element in the list:
useEffect( () => {
if(list.length <= 0) return
//Do something with the first element of the list
//Remove first element of the list
}, [list])
In this way, the effect trigger itself n times where n is the number of the element in the list.
Then i have another method in my component that insert elements in the list with the setList() method, let's call it
insertElemInList = () => {
//insert one or more elements in list
}
when insertElemInList is called, the useEffect trigger and start working for n times.
I don't know how many times the insertElemInList() is called, and how many elements is inserted every time, since this method is called after some actions of the user on the page.
So if an user call the insertElemInList() two or more times, before the last iteration of the effect is finished, then the effect trigger in the wrong way, in fact it will activate due to the change of state given by insertElemInList, but also by itself, resulting in more iterations and wrong behaviour.
So i'm trying to figure out how to use something inside the effect that doesn't trigger the effect itself, but can be used correctly.
for example I was thinking of modifying the effect and the state adding
const [semWait, setSem] = useState(1)
and then, continue to update the list state with the insertElemInList() method, but now:
useEffect( () => {
let doSomething = () => {
if(list.length < 0) return
//Do Something with the first element of the list
//Remove first element from the list
if(list.length > 0) doSomething()
}
doSomething()
setSem(1)
}, [semWait])
insertElemInList = () => {
//insert one or more elements in list
if(semWait == 1) setSem(0)
}
the above code is just an example of how I can solve the problem, I don't think it is the best solution and I gave you this example just to make you understand what I would like to do.
however, as you can see in this way I could add as many value as i want to my state whith insertElemInList() ​​and trigger the effect only if it is not already active (in other word, only if the semaphore is reset by the effect itself). However, I know it's not a good thing to use a state in the effect, without including it in dependencies, and if i add the state list as dependency of the useEffect the problem return.
the problem is that I can't figure out how to use a value inside useEffect without including it in the dependency
EDIT:
sorry for the late reply, i tried to implement this code on my own but there are workflow problems in my work, i'll try to explain the problems:
the code is a snippet to download some file from an API, the user on the site have a list of files to download, he can click on the files to download them as many times as he wants. my intent is to create a request queue, so as not to send too many requests to the server.
the code below show my work, i've inserted some comments to let you figure out how my code should work:
const [queue, setQueue] = useState({
"op_name": "NO_OP"
})
//file download function
let requestFileDownload = (fileId) => {
/*
This function construct the object to put in queue state and call the method 'insertInQueue'
*/
let workState = appState
insertInQueue({
"op_name": "DOWNLOAD_FILE",
"file_id": fileId,
"username": workState.user.username,
"token": workState.user.token
})
}
//Function insertInQueue to insert an element in the queue
let insertInQueue = (objQueue) => {
//Some control to check if the object passed exist, and have valid fields
if (!objQueue || !objQueue.op_name || objQueue.op_name === "NO_OP") return //nothing to insert in queue
//calling method to insert in timeline div, this work only whith front-end dom elements (full synchronous)
insertElemInTimeline(objQueue.op_name)
//setting timeout in which try to insert the object passed in queue
setTimeout(function run() {
let workQueue = queue //gettind queue object
if (workQueue && workQueue.op_name === "NO_OP") {
/*
if queue exist and the op_name is "NO_OP", this mean that the previus operations on the queue
is finished, so we can start this operation
*/
setQueue(objQueue) //set the queue with the object passed as paramether to trigger the effect
return;
}
// if the queue op_name was != "NO_OP" call the function again for retry to insert in 1 second
setTimeout(run, 1000)
}, 0)
}
//Effect triggered when queue object change
useEffect(() => {
if (queue.op_name === "NO_OP") return //no operation to do
//Effective file download
let downloadFileEffect = async () => {
let objQueue = queue //getting queue state
//Two functions to download the element by calling backend api
let downloadFileResponse = await downloadFile(objQueue.file_id, objQueue.username, objQueue.token)
download(downloadFileResponse.data, downloadFileResponse.headers['x-suggested-filename'])
//after the method have completed, i can set a new state for the queue with "op_name": "NO_OP"
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
//method for remove the element from the dom
removeElemFromTimeline()
}
//calling function to trigger the donwload.
downloadFileEffect()
}, [queue])
now the problem is, that when i try to reset the queue state in the effect, when i call:
let appoStateQueue = {
"op_name": "NO_OP"
}
setQueue(appoStateQueue)
the queue is not resetted in the case the user have clicked two download one after the first is running.
In fact the queue stops with the first object inserted in it, and is not reset by the effect, so the second download never starts, because it sees forever the queue occupied by the first download.
In case user click one download, then wait for the download, and only then click the second, then there's no problem, and the queue is resetted correctly by the effect
First, useEffect doesn't run for "trigger itself n times where n is the number of the element in the list". useEffect will run every time list changes in length or resides in a different memory space than it did in a previous render. This is how "shallow" comparison works with javascript objects in react. Your main issue is that you are changing your dependency from within the effect. This means that while the effect runs, it updates the dependency and forces it to run again and again and again...memory leak!
Your solution might work, but as you stated is not best practice. A better solution (imo) would be to allow for a "parsedList" state that can be the end result of parsing the list. Let the source of truth with the list only be impacted by the client interaction. You monitor these changes and change your parsedList based on these changes.

Issue with the $mdDialog.show(confirm), not able to execute the code below this dialog directives in AngularJS

I'm finding this issue with $mdDialog.show(confirm), the code below the dialog function executes even before the popup arise.
I can handle by setting a flag to restrict the execution of below code. but after the completion of the dialog activity it goes to Error(ie, resolvePromise()) without executing the below code:
//This is the code I have used.
var confirm = $mdDialog.confirm()
.title('Early Dispatch Date Available.')
.textContent('Do you want to reschedule and Change Quantity.')
.ariaLabel('day')
.ok('Yes')
.cancel('Plan on Date');
$mdDialog.show(confirm).then(function (obj)
{
$scope.targetDates[lastItem].Qty = 0;
return;
}, function () {
condition = 'QtyLessThanCap';
});
//for example,this is the code which gets executed even before the comfirmation dialog pops up..
angular.forEach($scope.targetDates, function (Datess)
{
totalCalQty = totalCalQty + parseInt(Datess['Qty']);
});
I want the codes to be executed in the order I have coded them,I mean the below code need to be executed only after the confirmation dialog activity.
Thank you in advance.
The only way I've been able to deal with the same type of functionality is by splitting the function, so for example you have an action function that determines whether you need a modal or can just calculate the total:
function something() { // if modal show modal.then(doSomething) }
then the actual calculation function which can be called directly, or from any level of mdModal.then():
function doSomething() { // calculate total }
It's probably not as pretty as you're looking for but it works.

How to display progress to user during long running process in React

I am building an app where a user provides a file and some parameters to then perform a long running task. I have all of that working. What is not working is showing the user the current progress of processing. I have a simple CodePen set up to illustrate.
In the Pen, I have a button that runs a task in a while loop. If I am looking at the console, I can see the progress printing out as we step through the loop. However, the state isn't updating until the loop is done, so in the UI the progress jumps from 0 to 5 without displaying the intermediate values. Here I am simulating the task with the sleep function, I do not actually use the sleep function in my app.
I've done some research and I know this has to do with setState being asynchronous and with React batching updates together to be more efficient with rendering the UI.
With that being said, I am wondering what the best way to display progress to the user would be. Using React's state doesn't work, and I've tried directly writing to the DOM but that wasn't working (and it didn't seem like a clean way to do it). Do I need to use some additional library to do this or is there something I am missing? I was considering moving this to a separate process and then communicating the progress back to the app, but wouldn't I run into the same issue of the UI not updating?
Also potentially important, I am using the while loop because I am using a generator, so I know I won't receive too many progress updates because yield runs each percentage point from 0 to 100. It is also easy for me to remove the generator/yield part if that would be better.
My code is in CodePen as well as below:
---HTML---
<div id="app"></app>
---JSX---
class Application extends React.Component {
constructor(props) {
super(props);
this.state = {
progress: 0
};
this.doTask = this.doTask.bind(this);
this.sleep = this.sleep.bind(this);
}
sleep(milliseconds) {
var start = new Date().getTime();
for (var i = 0; i < 1e7; i++) {
if ((new Date().getTime() - start) > milliseconds){
break;
}
}
}
doTask() {
let count = 0;
while(count<5) {
count++;
console.log(count);
this.setState({
progress: count
});
this.sleep(500);
}
}
render() {
return <div>
<button onClick={this.doTask}>Do Task</button>
<div>Progress: {this.state.progress}</div>
</div>;
}
}
/*
* Render the above component into the div#app
*/
React.render(<Application />, document.getElementById('app'));
This is a recurring issue in development, so I hope this question and solutions helps you out: Calling setState in a loop only updates state 1 time.
I would also take a look at https://jsbin.com/kiyaco/edit?js,output where an alternate form of this.setState is used. It essentially passed in a function which has access to the current state.
What I ended up doing was creating a background process (aka a hidden window because I'm using electron) that basically acts as a server. The information for my long running process is sent to the background process via a websocket and the progress information is sent back to my main component also via a websocket. Updating the state from a loop seems to me to be more intuitive, but running my code in the background doesn't freeze up the UI and now I get my desired behavior.
I have the same issue and used a workaround. For some reason, when using setTimeout() the page does the setState() processing correctly. It remembered me of runLater in JavaFX.
So this is a workaround I used:
// a wrapper function for setState which uses `Promise` and calls `setTimeout()`
setStatePromise = (state) => new Promise(resolve => {
this.setState(state, () => {
setTimeout(() => resolve(), 1);
});
});
Now you can call this wrapper instead. But you'll need to use async functions:
async doTask() {
let count = 0;
while(count<5) {
count++;
console.log(count);
await this.setStatePromise({
progress: count
});
this.sleep(500);
}
}

lodash debounce not-working (I use it wrongly?), function called multiple times

UPDATE: Answered by myself. Scroll to the end.
My objetive is simple:
I have a google map canvas. User can drag the map, and the program automatically downloads the incidents around the center of the map (and draw the markers).
I have this listener for plugin.google.maps.event.CAMERA_CHANGE events. The thing is, this listener triggers multiple times. Meaning: from the time you tap your finger on the canvas -> dragging in accros the canvas -> to the time you lift your finger ... the event triggers multiple times. Not just when you lift your finger.
Apparently it has a watcher that triggers every N miliseconds.
I dont't want my code to perform that costly downloading data from the server + drawing markers during those interim camera_changes. I want to do it only after the user stop dragging. That means: on the last camera change event received during 5 seconds (I figured that the slowest user takes 5 seconds to drag from corner to the opposite corner of the canvas).
Obviouly I turn to debounce for this need. But it doesn't seem to work. I can see from the logs (X1, X2).... that the function gets called multiple times (around 3-to-4 times, depends on how fast you drag across the canvas).
Indeed, they get called only after I stop dragging. But, they get called in series. All 3-4 of them. With delay of 5 seconds between invocation.
That's not what I expected. I also added invocation to the .cancel method (which I think is superfluous..., because if I understand it correctly, the debounce should've already handled that; cancelling interim-invocations within the timeout).
I also tried throttle (which I think conceptually is not the answer. Debounce should be the answer). Anyway, same issue with throttle.
So, my question: where did I do wrong (in using lodash's debounce)?
Thanks!
var currentPosition = initialPosition();
drawMarkersAroundCenter(map, currentPosition);
var reactOnCameraChanged = function(camera) {
console.log('X1');
console.log('-----');
console.log(JSON.stringify(camera.target));
console.log(JSON.stringify(currentPosition));
console.log('-----');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
return;
}
currentPosition = camera.target;
drawMarkersAroundCenter(map, currentPosition);
}
var debouncedReactOnCameraChange = lodash.debounce(reactOnCameraChanged, 5000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
--- UPDATE ---
I tried a very simplified scenario of using debounce on nodejs console, it works as I expected. I don't even invoke .cancel in the code below. So what's wrong with the above code? I can't see any difference with this simplified code in the image below.
UPDATE
I tried with this dude method instead of "reactOnCameraChanged":
var dude = function(camera) {
console.log('dude');
}
var debouncedReactOnCameraChange = lodash.debounce(dude, 5000, {
'leading': false,
'trailing': true
});
And I also removed the invocation to .cancel:
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
//debouncedReactOnCameraChange.cancel();
debouncedReactOnCameraChange(camera);
});
I can see the 'dude' gets printed only once during those 5 seconds.... So.., something that I do inside reactOnCameraChanged is causing interference ... somehow....
RESOLVED:
See answer below.
This code works:
var currentPosition = latLng;
drawMarkersAroundCenter(map, currentPosition);
var debouncedReactOnCameraChange = lodash.debounce(function(camera) {
console.log('reactOnCameraChanged: ' + JSON.stringify(currentPosition));
drawMarkersAroundCenter(map, currentPosition);
}, 3000, {
'leading': false,
'trailing': true
});
map.on(plugin.google.maps.event.CAMERA_CHANGE, function(camera) {
console.log('CAMERA_CHANGE');
if (camera.target.lat == currentPosition.lat && camera.target.lng == currentPosition.lng) {
console.log('same camera spot');
return;
}
console.log('different camera spot');
currentPosition = camera.target;
debouncedReactOnCameraChange(camera);
});

how to notify about $interval end in angularjs

I want to make custom animation in my web application, and i'm using $interval method to do some visual effects.
And all i want is to know, when that animation ends.
For example
var myInterval = $interval(function(){
//do some staff
}, 10, 20);
How can i get notify about that interval ends? Except $timeout(fn, 200), of course.
And the second question is also about notifying, but in case, when i cancel that interval is other place manually by $interval.cancel(myInterval), can i get notified about that?
For the first case, you can just do:
myInterval.then(function () { console.log("Finished."); } );
It isn't a good idea to count the number of iterations when Angular is already doing that for you.
See https://docs.angularjs.org/api/ng/service/$q
You can broadcast an event by yourself when canceling the interval. Take a look at $rootScope.$broadcast().
Where you cancel the interval:
$rootScope.$broadcast('Custom::Event');
Where you want to retrieve the broadcast:
$scope.$on('Custom::Event', function (e) { ... });
Edit: Iterations
If you want to send the broadcast after the last iteration, check the first parameter provided to the function for the $interval.
$interval(function (iteration) {
// Do some stuff...
// Broadcast after last iteration
if (iteration === iterations - 1) {
$scope.$broadcast('Interval::Finished');
}
}, delay, iterations);
See: JS Bin

Resources