click event listener fires immediately - javascript

I have a class that handle the Education section of my web page:
class Education{
constructor(){
this.courses = [
{id:'css-course',state:'70'},
{id:'vue-course',state:'85'},
{id:'text-mining-course',state:'50'}
];
this.eduButton = document.getElementById('edu-button');
this.init()
}
init(){
this._initEducation()
}
_initEducation(){
let self = this;
this.eduButton.addEventListener('click',function(){
self._initTimeline().play();
})
this.eduButton.previousElementSibling.addEventListener('click',function(){
self.courses.forEach(function(course){
document.getElementById(course.id).style.width = '0%'
})
})
}
_initTimeline(){
const courseTimeline = new TimelineLite();
this.courses.forEach(function(course){
let curr = document.getElementById(course.id)
courseTimeline.to(curr,.3,{width:`${course.state}%`},'width')
})
return courseTimeline
}
}
module.exports = {
Education
}
the _initEducation method is quite similar to another method that i have in the About section, both of them serve to animate a list of progress bar with gsap:
_initAbout:
_initAbout(){
let self = this;
this.aboutButton.addEventListener('click',function(){
self._initTimeline().play();
})
this.aboutButton.previousElementSibling.addEventListener('click',function(){
self.skills.forEach(function(skill){
document.getElementById(skill.id).style.width = '0%'
})
})
}
Because this two methods are similar, i wrote an utility class named InitProgressBarAnimation:
class InitProgressBarAnimation{
init(){
return{
start:this._start,
reset:this._reset
}
}
_start(button,timeline){
button.addEventListener('click',function(){
console.log(timeline)
timeline.play()
})
}
_reset(button,data){
button.previousElementSibling.addEventListener('click',function(){
console.log('reset')
data.forEach(function(d){
document.getElementById(d.id).style.width = '0%'
})
})
}
}
and replace, for testing the initEducation method with this:
class Education{
constructor(ProgressBarAnimationHandler){
...
this.ProgressBarAnimationHandler = ProgressBarAnimationHandler;
this.init()
}
_initEducation(){
this.ProgressBarAnimationHandler.start(this.eduButton,this._initTimeline());
this.ProgressBarAnimationHandler.reset(this.eduButton,this.courses);
/*
let self = this;
this.eduButton.addEventListener('click',function(){
self._initTimeline().play();
})
this.eduButton.previousElementSibling.addEventListener('click',function(){
self.courses.forEach(function(course){
document.getElementById(course.id).style.width = '0%'
})
})
*/
}
Now seems that when i load the page, the timeline start doing his job (i can see the style witdh attribute fill itself with the value), and if i close the section the reset function works but then the start function doesn't start again. How that possible? it is a problem with gsap?
the start method only attach an handler to the button...

Remove the "()" from _initTimeline. The () tell the function to execute. If you leave them off you are just passing the function to the event handler to execute. This is the standard pattern for what you are trying to do. Finally it would look like this:
this.ProgressBarAnimationHandler.start(this.eduButton,this._initTimeline);

Related

Can not remove event listener using setTimeout on element using class method

So I have this function (App.btnInteractive) that adds or removes mouseover and mouseout event listeners according to the boolean passed into the function call
'use strict';
const btnStart = document.querySelector('.btn--start');
class App {
constructor() {
btnStart.addEventListener('click', this.start.bind(this));
}
btnInteractive(state) {
console.log(`Check: ${state}`);
function mouseover() {
console.log('over');
}
function mouseout() {
console.log('out');
}
if (state) {
btnStart.addEventListener('mouseover', mouseover);
btnStart.addEventListener('mouseout', mouseout);
} else {
btnStart.removeEventListener('mouseover', mouseover);
btnStart.removeEventListener('mouseout', mouseout);
}
}
start() {
this.btnInteractive(true);
setTimeout(() => {
this.btnInteractive(false);
}, 2000);
}
}
const app = new App();
That results in console output below after clicking button and moving cursor out of the button and on it after each function call
Check: true
out
over
Check: false
out
over
So here's my trail of thought:
Both if and else code blocks execute
Stripping the code down (resulted in code in question itself)
Removing click event listener doesn't help
Removing both click and mouseout also doesn't work
Idea is that after 2 seconds passed the mouseover and mouseout event listeners should be removed.
I'm not an expert in Javascript, but i think that when you try to remove the event listeners, you need to have the real reference to the function you used before.
When you execute it again inside the setTimeout, it is trying to remove the reference to the new functions "mouseover" and "mouseout" that were created.
Try setting the callbacks for the listeners like that:
if (state) {
btnStart.onmouseover = mouseover;
btnStart.onmouseout = mouseout;
} else {
btnStart.onmouseover = () => {};
btnStart.onmouseout = () => {};
}

Concatenate function

The idea behind this to animate section with mousewheel - keyboard and swipe on enter and on exit. Each section has different animation.
Everything is wrapp inside a global variable. Here is a bigger sample
var siteGlobal = (function(){
init();
var init = function(){
bindEvents();
}
// then i got my function to bind events
var bindEvents = function(){
$(document).on('mousewheel', mouseNav());
$(document).on('keyup', mouseNav());
}
// then i got my function here for capture the event
var mouseNav = function(){
// the code here for capturing direction or keyboard
// and then check next section
}
var nextSection = function(){
// Here we check if there is prev() or next() section
// if there is do the change on the section
}
var switchSection = function(nextsection){
// Get the current section and remove active class
// get the next section - add active class
// get the name of the function with data-name attribute
// trow the animation
var funcEnter = window['section'+ Name + 'Enter'];
}
// Let's pretend section is call Intro
var sectionIntroEnter = function(){
// animation code here
}
var sectionIntroExit = function(){
// animation code here
}
}();
So far so good until calling funcEnter() and nothing happen
I still stuck to call those function...and sorry guys i'm really not a javascript programmer , i'm on learning process and this way it make it easy for me to read so i would love continue using this way of "coding"...Do someone has a clue ? Thanks
Your concatenation is right but it'd be better if you didn't create global functions to do this. Instead, place them inside of your own object and access the functions through there.
var sectionFuncs = {
A: {
enter: function() {
console.log('Entering A');
},
exit: function() {
console.log('Exiting A');
}
},
B: {
enter: function() {
console.log('Entering B');
},
exit: function() {
console.log('Exiting B');
}
}
};
function onClick() {
var section = this.getAttribute('data-section');
var functions = sectionFuncs[section];
functions.enter();
console.log('In between...');
functions.exit();
}
var buttons = document.querySelectorAll('button');
for (var i = 0; i < buttons.length; i++) {
buttons[i].addEventListener('click', onClick);
}
<button data-section="A">A</button>
<button data-section="B">B</button>
You could have an object that holds these functions, keyed by the name:
var enterExitFns = {
intro: {
enter: function () {
// animation code for intro enter
},
exit: function () {
// animation code for intro exit
}
},
details: {
enter: function () {
// animation code for details enter
},
exit: function () {
// animation code for details exit
}
}
};
var name = activeSection.attr('data-name');
enterExitFns[name].enter();

jQuery plugin instances variable with event handlers

I am writing my first jQuery plugin which is a tree browser. It shall first show the top level elements and on click go deeper and show (depending on level) the children in a different way.
I got this up and running already. But now I want to implement a "back" functionality and for this I need to store an array of clicked elements for each instance of the tree browser (if multiple are on the page).
I know that I can put instance private variables with "this." in the plugin.
But if I assign an event handler of the onClick on a topic, how do I get this instance private variable? $(this) is referencing the clicked element at this moment.
Could please anyone give me an advise or a link to a tutorial how to get this done?
I only found tutorial for instance specific variables without event handlers involved.
Any help is appreciated.
Thanks in advance.
UPDATE: I cleaned out the huge code generation and kept the logical structure. This is my code:
(function ($) {
$.fn.myTreeBrowser = function (options) {
clickedElements = [];
var defaults = {
textColor: "#000",
backgroundColor: "#fff",
fontSize: "1em",
titleAttribute: "Title",
idAttribute: "Id",
parentIdAttribute: "ParentId",
levelAttribute: "Level",
treeData: {}
};
var opts = $.extend({}, $.fn.myTreeBrowser.defaults, options);
function getTreeData(id) {
if (opts.data) {
$.ajax(opts.data, { async: false, data: { Id: id } }).success(function (resultdata) {
opts.treeData = resultdata;
});
}
}
function onClick() {
var id = $(this).attr('data-id');
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, id);
}
function handleOnClick(parentContainer, id) {
if (opts.onTopicClicked) {
opts.onTopicClicked(id);
}
clickedElements.push(id);
if (id) {
var clickedElement = $.grep(opts.treeData, function (n, i) { return n[opts.idAttribute] === id })[0];
switch (clickedElement[opts.levelAttribute]) {
case 1:
renderLevel2(parentContainer, clickedElement);
break;
case 3:
renderLevel3(parentContainer, clickedElement);
break;
default:
debug('invalid level element clicked');
}
} else {
renderTopLevel(parentContainer);
}
}
function getParentContainer(elem) {
return $(elem).parents('div.myBrowserContainer').parents()[0];
}
function onBackButtonClick() {
clickedElements.pop(); // remove actual element to get the one before
var lastClickedId = clickedElements.pop();
var parentContainer = getParentContainer($(this));
handleOnClick(parentContainer, lastClickedId);
}
function renderLevel2(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderLevel3(parentContainer, selectedElement) {
$(parentContainer).html('');
var browsercontainer = $('<div>').addClass('myBrowserContainer').appendTo(parentContainer);
//... rendering the div ...
// for example like this with a onClick handler
var div = $('<div>').attr('data-id', element[opts.idAttribute]).addClass('fct-bs-col-md-4 pexSubtopic').on('click', onClick).appendTo(subtopicList);
// ... rendering the tree
var backButton = $('<button>').addClass('btn btn-default').text('Back').appendTo(browsercontainer);
backButton.on('click', onBackButtonClick);
}
function renderTopLevel(parentContainer) {
parentContainer.html('');
var browsercontainer = $('<div>').addClass('fct-page-pa fct-bs-container-fluid pexPAs myBrowserContainer').appendTo(parentContainer);
// rendering the top level display
}
getTreeData();
//top level rendering! Lower levels are rendered in event handlers.
$(this).each(function () {
renderTopLevel($(this));
});
return this;
};
// Private function for debugging.
function debug(debugText) {
if (window.console && window.console.log) {
window.console.log(debugText);
}
};
}(jQuery));
Just use one more class variable and pass this to it. Usually I call it self. So var self = this; in constructor of your plugin Class and you are good to go.
Object oriented way:
function YourPlugin(){
var self = this;
}
YourPlugin.prototype = {
constructor: YourPlugin,
clickHandler: function(){
// here the self works
}
}
Check this Fiddle
Or simple way of passing data to eventHandler:
$( "#foo" ).bind( "click", {
self: this
}, function( event ) {
alert( event.data.self);
});
You could use the jQuery proxy function:
$(yourElement).bind("click", $.proxy(this.yourFunction, this));
You can then use this in yourFunction as the this in your plugin.

How can I set the state of a component on video end in react?

In my component I have an componentDidUpdate function where I play a video and on that video I set the video.onended event as noted HERE
Currently my code looks like this:
componentDidUpdate: function() {
if(this.state.showVideo){
this.refs.homeVideo.play();
// Triggering event on video end
let homeVideo = document.getElementById("homeVideo");
homeVideo.onended = function(){
console.log(this.state);
this.setState({ showVideo: !this.state.showVideo });
}
}
}
My issue right now is that this.state is undefined in the onended function and so is setState, which is preventing me from updating the state of the component in react so that I can close the video player when it ends.
What is the appropriate react way of handling this?
You don't need document.getElementById.
Try to update your code to this:
componentDidUpdate: function() {
var self = this;
if(this.state.showVideo){
let video = this.refs.homeVideo;
video.play();
video.onended = function(){
console.log(self.state);
self.setState({ showVideo: !self.state.showVideo });
}
}
}
JSfiddle example https://jsfiddle.net/ntfjncuf/
Its because every new function defined its own this value.
You can do something like:
var self = this;
homeVideo.onended = function(){
console.log(self.state);
self.setState({ showVideo: !self.state.showVideo });
}
Or better yet, use arrow functions if your using ES6:
homeVideo.onended = () => {
console.log(this.state);
this.setState({ showVideo: !this.state.showVideo });
}
Arrow functions lexically binds the this value.

Event Listener?

I have an infinite scroll class:
(function(){
"use strict";
var InfiniteScroll = function() {
this.init();
};
var p = InfiniteScroll.prototype = mc.BaseClass.extend(gd.BaseClass);
p.BaseClass_init = p.init;
/*
* Public properties
*/
p.loading = false;
/*
* Public methods
*/
p.init = function() {
// Super
this.BaseClass_init();
// Init
this.ready();
};
p.ready = function() {
this._initInfiniteScroll();
};
p._initInfiniteScroll = function() {
$(window).scroll(function(){
if($(window).scrollTop() == $(document).height() - $(window).height()){
if(!p.loading)
{
p.loading = true;
//send a message up to say loading
}
}
});
}
mc.InfiniteScroll = InfiniteScroll;
}(window));
Now this is called from another class by:
this.infiniteScroll = new mc.InfiniteScroll();
In the other class I wish to listen for when the scroll is fired from where I have the comment:
//send a message up to say loading
I was just wondering how I could go about this, I'm familiar with event listeners in AS3, but could someone point me in the right direction for js?
You can implement a custom event handler in your class on which you add listener functions in other classes like in this post.
Which you would use in your _initInfiniteScroll function like:
//send a message up to say loading
this.fire({ type: "ContentLoadingEvent" });
Though, as suggested in the post, creating a seperate class for your InfiniteScroll events is better.

Categories

Resources