How can I input a Vue.js object for the current item into another Javascript function on the page?
I have a slide HTML page that only displays the current HTML ID
Each slide I have has a time in seconds pulled in from my JSON, the timer object. I need to put this number in seconds into a SetInterval function to load programmatically the next slide.
var seconds = parseInt(document.querySelector("#value").innerText) * 1000;
setInterval(function () {
document.querySelector("a.o-controls2").click();
}, seconds)
You can see the example in action here.
I have tried loading the expression value but get a null result in the console. So perhaps this has something to do with the page load and how Vue renders the page?
I have also tried the following, by loading in the Vue object but this didn't work either:
var seconds = (this.timer - 1) 1000;
setInterval(function () { document.querySelector("a.o-controls2").click();
}, seconds)
the easiest way (IMHO) to access the data after some change (in this case, after fetch) is to use $emit and $on
this.items = items.reverse();
this.$nextTick(() => this.$emit('dataLoaded', this.items));
you could also use it without the nextTick, but then you'd get the data back before the dom is updated, so your js would fail.
this.items = items.reverse();
this.$emit('dataLoaded', this.items);
then listen to the change using...
app.$on('dataLoaded', items => {
console.log('items', items)
document.querySelector("a.o-controls2"); // now accessible
// do stuff
})
here is a fiddle: https://jsfiddle.net/y32hnzug/1/
it doesn't implement the click timing though since that's outside the scope of the question
The null response is the return from querySelector(); it's returning null because the external script is running before Vue has rendered the application. When the script runs, there is no element with an id of "value" in the document.
The easiest fix might be to move that code into the mounted() lifecycle hook of the app; possibly within a nextTick() to make sure that all child components have also rendered.
Related
I'm kinda new to the react framework.
As per my requirement , I want to wait until data arrives and binds to my constants in my useEffect() method.
The data is sent encrypted from the main page and is decrypted as follows :
useEffect(() => {
const DecryptedGroupID = atob(groupID.toString());
const DecryptedFactoryID = atob(factoryID.toString());
setProfitAndLossDetails({
...ProfitAndLossDetails,
groupID: DecryptedGroupID,
factoryID: DecryptedFactoryID
});
}, []);
I want to add a waiter/timer/delay to wait until the data gets bound to my const variables. (atob is a decrypting function).
The groupID is required to generate the factoryIDs for the specific group, hence during page reload since it takes time / delay for the hooks to bind, the factoryIDs won't load at times(however when refreshing it appears sometimes), I think adding a delay and giving it time to bind might fix this issue.
Just add groupID and factoryID as your useEffect dependencies. Hook will be called automatically when they are changed. Inside hook you can check if groupID and factoryID not empty, and call your setter function.
Read more about how this hook work:
https://reactjs.org/docs/hooks-reference.html#useeffect
You need to:
Test in the hook if they are defined or not
Call the effect hook when the values you are depending on change (so the hook doesn't run once when they aren't defined and then never run again) — add them to the dependency array.
Such:
useEffect(() => {
if (!groupID || !factoryID) return;
// Otherwise don't return and use them with their values
}, [groupID, factoryID]);
I'm using React to build my project. I made a chat button by using external script. And I'd like to disappear that button in some specific pages. I can't use a ref to access that button's element So I used document.getElementById.
But my problem is my code sometimes returns error. (I think when my code runs, chat button didn't run by external script.) How can I solve this problem?
useEffect(() => {
//access chat button element by using document.getElementById
const chatBtn = document.getElementById('ch-plugin-launcher');
//if it doesn't exist in current page, it returns.
if (!chatBtn) {
return;
}
//if a button exists, it will be hide.
chatBtn.classList.add('hide');
return () => {
chatBtn.classList.remove('hide');
};
}, []);
I think the error in return of useEffect. You return a function, which can be called at any time whenever the chat button does not exist. Add check for existing on the chat button in the useEffect return function. Other code looks well.
useEffect(() => {
// Your code
return () => {
document.getElementById('ch-plugin-launcher')?.classList?.remove('hide');
};
});
I think #0x6368656174 answwer is correct.
Just for more clarification why:
When exactly does React clean up an effect? React performs the cleanup
when the component unmounts. However, as we learned earlier, effects
run for every render and not just once. This is why React also cleans
up effects from the previous render before running the effects next
time. We’ll discuss why this helps avoid bugs and how to opt out of
this behavior in case it creates performance issues later below.
Source: https://reactjs.org/docs/hooks-effect.html#example-using-hooks-1
I have a calendar component that asynchronously fetches the calendar events on componentDidMount. the API might return a response that says that the calendar is unavailable right now so in that case I want the component to keep re-fetching the data every 5 seconds until it succeeds.
I tried something like
componentWillReceiveProps(nextProps){
if(nextProps.data.calendarBuildStatusCode){
this.setState({isBuildingCalendar: true});
setTimeout(()=>{
this.fetchEvents();
}, 5000)
}else{
this.setState({isBuildingCalendar: false});
}
}
where nextProps.data.calendarBuildStatusCode is the indication that I get from the API that the calendar is still not available. and this.setState({isBuildingCalendar: true}); is simply in charge of displaying a spinner until the calendar is available.
this.fetchEvents(); is a call to the API to get the calendar events, it will cause a re-render.
The code above doesn't work (and I knew it wouldn't), I was just trying to give a sense of what I'm trying to achieve but don't know how.
Any suggestions on how to deal with re-rendering using setTimout until prop is changed would be great.
All you need to do is to make use of clearTimeout and use it within your fetchEvents api call
componentWillReceiveProps(nextProps){
if(nextProps.data.calendarBuildStatusCode){ // Probably you should also add a conditional check here that signifies a change in data, otherwise everytime `calendarBuildStatusCode` is `true` and `componentWillReceiveProps` is called this function will execute.
this.timerID = setTimeout(()=>{
this.fetchEvents();
}, 5000)
}
}
Now within the fetchEvents function
fetchEvents(){
ApiCall().then(() => {
clearTimeout(this.timerID);
});
}
I am using Ember-cli in my web app. I have a countdown component to show a countdown timer on UI. Here is my component code.
export default Ember.Component.extend({
end_time: 0,
start_time: 0,
some_id: 0,
timer: 0, // Show this in UI - {{timer}} Seconds
init: function() {
this._super();
let end_time = this.get("end_time"), // 1479476467693
start_time = this.get("start_time"), // 1479476381491
some_id = this.get("some_id");
let wait_time = Math.floor((end_time - start_time)/1000);
this.set('timer', wait_time);
let timerName = "timer_" + some_id;
let _self = this;
window.initTimer.someTimer[timerName] = setInterval(function() {
_self.set('timer', wait_time);
if(wait_time <= 0) {
clearInterval(window.initTimer.someTimer[timerName]);
}
wait_time --;
}, 1000);
}
});
This component works fine, if I add this to a single route.
Now, I have added this component to both parent route and child (/:ID) route, since I need to show the component on both templates. In the child (/:ID) template, I have a button to clear the timer. So I have added this code for that button action.
buttonAction: function(some_id) {
let timerName = "timer_" + some_id;
clearInterval(window.initTimer.someTimer[timerName]);
}
Strangely, when the buttonAction is called, the timer on the child template alone is cleared. The timer on parent template keeps running. But both the timer are assigned to a single global variable (window.initTimer.someTimer) and should be cleared when I run clearInterval().
Is there any solution for clearing the timer on both parent route and child route on click of a button, which resides on child template? Couldn't figure out what magic Ember is playing with global variables!!
Ember is doing no magic here, but your code is much to complicated!
The interesting question is from where some_id comes. If its not the same for both then with which one are you calling buttonAction?
Assume you have the ids one and two. Then you have the two intervals at window.initTimer.someTimer.timer_one and window.initTimer.someTimer.timer_two. Now if you clear window.initTimer.someTimer.timer_one why should window.initTimer.someTimer.timer_two be cleared as well? Well, its not, and thats why your code is not working.
Assume you only have one id, lets call is theOnlyOne for both timers.
Then the init hook of the second component will reassign window.initTimer.someTimer.timer_theOnlyOne, and so only this second component can be resetted when you call buttonAction. Then thats why your code is not working.
So what should you do now? First, you really should stop using the global object! There are so much better ways to do this in the ember ecosystem.
For your timers you should check out ember-concurrency.
If you want to use a global state you should use a service. However I don't recommend this for your problem, because it's against the DDAU principle. However to tell you whats the right way to do what you want to do we need to understand why you have this timers, how they are related and why you want to cancel them both with one click. Probably you would have some timer state outside of the components and pass it down to the components. Maybe a util can be helpful. But this really depends on your use-case.
I've got MDL running with React at the moment and it seems to be working fine at the moment.
I've got the Progress Bar appearing on the page as needed and it loads up with the specified 'progress' on page load when either entering in a number directly:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(10);
})
or when passing in a number via a Variable:
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
It stops working after this though. I try to update the value via the Variable and it doesn't update. I've been advised to use this:
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(45);
to update the value but it doesn't work. Even when trying it directly in the console.
When trying via the Console I get the following Error:
Uncaught TypeError: document.querySelector(...).MaterialProgress.setProgress is not a function(…)
When I try to increment the value via the Variable I get no errors and when I console.log(value) I am presented the correct number (1,2,3,4...) after each click event that fires the function (it fires when an answer is chosen in a questionnaire)
What I want to know is if there's something obvious that I'm missing when using MTL and React to make components to work? There was an issue with scope but I seem to have it fixed with the following:
updateProgressBar: function(value) {
// fixes scope in side function below
var _this = this;
document.querySelector('#questionnaireProgressBar').addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(value);
})
},
In React I've got the parent feeding the child with the data via props and I'm using "componentWillReceiveProps" to call the function that updates the progress bar.
I've used the "componentDidMount" function too to see if it makes a difference but it still only works on page load. From what I've read, it seems that I should be using "componentWillReceiveProps" over "componentDidMount".
It's being fed from the parent due to components sending data between each other. I've used their doc's and some internet help to correctly update the parent function to then update the progress bar in the separate component.
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.props.updateProgressBarValue(questionsAnsweredTotal);
}
The parent function looks like the following (I think this may be the culprit):
// this is passed down to the Questions component
updateProgressBarTotal: function(questionsAnsweredTotal) {
this.setState({
progressBarQuestionsAnswered : questionsAnsweredTotal
})
}
I can post up some more of the code if needed.
Thank you
Looks I needed a fresh set of eyes on this.
I moved the function to the child of the parent. It seems that using document.querySelector... when in the parent doesn't find the element but when it's moved to the child where I do all the question logic it seems to be fine. It increments the progress correctly etc now :)
// goes to Questionnaire.jsx (parent) to update the props
updateProgressBarTotal: function(questionsAnsweredTotal) {
// updates the state in the parent props
this.props.updateProgressBarValue(questionsAnsweredTotal);
// update the progress bar with Value from questionsAnsweredTotal
document.querySelector('.mdl-js-progress').MaterialProgress.setProgress(questionsAnsweredTotal);
},
I had same problem in angular2 application.
You don't necessary need to move to the child component.
I found after struggling to find a reasonable fix that you simply have to be sure mdl-componentupgradedevent already occurred before being able to use MaterialProgress.setProgress(VALUE). Then it can be updated with dynamic value.
That is why moving to the child works. In the parent component mdl-componentupgraded event had time to occur before you update progress value
My solution for angular2 in this article
Adapted in a React JS application :
in componentDidMount, place a flag mdlProgressInitDone (initiated to false) in mdl-componentupgraded callback :
// this.ProgBar/nativeElement
// is angular2 = document.querySelector('.mdl-js-progress')
var self = this;
this.ProgBar.nativeElement.addEventListener('mdl-componentupgraded', function() {
this.MaterialProgress.setProgress(0);
self.mdlProgressInitDone = true; //flag to keep in state for exemple
});
Then in componentWillReceiveProps test the flag before trying to update progress value :
this.mdlProgressInitDone ? this.updateProgress() : false;
updateProgress() {
this.ProgBar.nativeElement.MaterialProgress.setProgress(this.currentProgress);
}
After attaching the progress bar to the document, execute:
function updateProgress(id) {
var e = document.querySelector(id);
componentHandler.upgradeElement(e);
e.MaterialProgress.setProgress(10);
}
updateProgress('#questionnaireProgressBar');