Recently, I have been integrating Material Design Lite into my React web application. For the most part, everything has worked out just fine, but currently I am having some issues with React's event handling, which doesn't seem to play nice with some MDL components.
In particular, I have a DOM element with an onClick handler, which works perfectly fine, until a MDL Tooltip is added, which causes the onClick to no longer fire. I've tried pretty much every variation possible (put the tooltip somewhere else in the DOM, attach the onClick handler to a container div which has the tooltip as a child, etc), and I just can't seem to get it to work.
Here's a JSBin that demonstrates the issue (I've also included an example that uses jQuery to bind a click handler to the element after the component mounts, which actually DOES work):
http://jsbin.com/sewimi/3/edit?js,output
I have some theories as to why this isn't working, but I don't know enough about either React or MDL to verify any of them.
I believe it has something to do with the way React handles events, and for some reason, MDL seems to be clashing with it. From the documentation:
React doesn't actually attach event handlers to the nodes themselves.
When React starts up, it starts listening for all events at the top
level using a single event listener. When a component is mounted or
unmounted, the event handlers are simply added or removed from an
internal mapping. When an event occurs, React knows how to dispatch it
using this mapping. When there are no event handlers left in the
mapping, React's event handlers are simple no-ops
This makes it seem like MDL might be messing with React's internal mapping of events, which causes my click on the element to become a no-op. But again, this is just a complete guess.
Does anyone have any ideas about this? I would prefer not to have to manually bind an event listener in componentDidMount for each of my components that use MDL Tooltips (like I did in the example JSBin I provided), but that's the solution I'm going with for now.
Also, since I was not sure if this was an MDL specific bug, I opted to post this question here instead of on their issues page. If anyone thinks I should post it there as well, let me know, and I can do that.
Thanks!
I faced this same issue too. I was trying to capture event clicks on a mdl-menu__item. And you are right in that React's synthetic event system is clashing.
What happens is that if an event happens inside your React component, your component will be the last to hear of the event. My work around was circumvent reacts event and use a react component which helps to attach native events react-native-listener.
<NativeListener onClickCapture={this.onListClick}>
<li className='mdl-menu__item' >
{...}
</li>
</NativeListener>
// This will be called by the native event system not react,
// this is in order to catch mdl-menu events and stop the menu from closing
// allowing multiple fields to be clicked
onListClick(field, event) {
event.stopImmediatePropagation();
// console.log('click');
}
My solution was for the mdl-menu but I'm sure it applies to the tooltip too.
A little late but
componentHandler.upgradeAllRegistered();
gets dynamically loaded elements working.
Note: that if you move the target element via CSS position the tooltip does not render to the new position automagically, you will need to id it and position it too.
Related
I'm integrating an HTML/CSS/JS template on a React SPA.
JS script (functions.js) uses jQuery to add event listeners when the script loads on multiple tags dynamically ($(p).addClass('.center'))
In order for it to work, the script must be added using useEffect for it to add the listeners when the component finishes rendering (in every component). Doing that, changing the route will replicate the event listeners already there for the parent component, thus, creating lag and unexpected behaviors in the app.
Here's what I have tried so far:
Having the script in index.html doesn't solve the problem. index.html loads only once, changing the route doesn't execute the script again.
Loading the script when the route changes add more duplicated event listeners since you can't destroy the old ones when the component dismounts.
Some advice on approaches I should try can help me solve the problem.
This is not recommended, and maybe not possible.
You should not manipulate the DOM from outside of React (including adding event handlers).
Anyway, you can add an extra script to a React-App, but you can not remove the script again. If this extra script is adding event handlers (and doesn't remove them),
then you can not remove the event handlers.
(You can not remove an added script because adding an extra script means
basically to execute some extra Javascript code, which can do whatever
it wants, including adding variables and functions, and even modify existing objects. You can not simply undo that.)
solutions (or not)
To achieve what you want you probably need to modify the imported script:
You can make the imported script manage the event handlers (i.e. removing them), or
you can give React the control over these event handlers (i.e. re-implement them in React),
and e.g. only keep the callbacks inside the imported script.
(Furthermore, you should make sure the script is only loaded once.)
Probably bad ideas:
If you can not remove event handlers, you may be able to remove the DOM nodes where the event handlers are attached to.
But working with the same DOM from React and from a separate script is likely to cause problems.
One might think of extracting the components that need the event handlers, and destroy them and recreate
them everytime you need a new event handler. But that sounds quite dirty to me,
and I would expect any kind of other errors later, including possibly memory leaks.
You would still re-load the same script file over and over again, only to attach a different event handler.
Old references to the DOM nodes might still stay in memory.
I am little confused,
I am using bootstrap theme in angular. Some one has written tab implementation as provided by bootstrap. But there is problem, it is in JavaScript. so events such as bs.tab.show are not captured by using observables. Observable.fromEvent does not run on subscription. The method used to trigger DOM event in bootstrap library is $(element).trigger('bs.tab.show', ...)
My basic understanding is whenever any event such as click, hover or in my case bs.tab.show is triggered from DOM, event goes in event stack, and observables is using same event stack to read events.
Could anyone point out difference?
It appears you may have the name of the events wrong.
If I use fromEvent with show.bs.tab instead, the event is triggered.
fromEvent(elem, 'show.bs.tab').subscribe(() => {
console.log("Heard")
})
Here is a StackBlitz demo
I'm using an module-project from Github on my Angular project, that lets me resize my DOM elements, which are drawn on top of a div, that works as a draw-board.
To shape my first elements, who are simple rectangles, by the way, i am using a mouseDown - mouseMove - mouseUp combination Event Listener, and then when i decide to resize one i hover over the edges and resize it.
Problem is that on the resizing event, which is a combination of resizestart - resizing - resizeend, although i know that the module is using and mouseDown-Move-Up Event listener and emits the events mentioned before, i cannot get the MouseEvent created, and instead i get the ResizeEvent, which doesn't have a stopPropagation() method, so calling it as it is gives an error(that it's not a function).
I need to stop Propagation, because when i resize my Elements on my draw-board the click event gets bubbled to the immediate parent element, and as a consequence i resize an existing element and create a new rectangle at the same time.
On top of that, the ResizeEvent doesn't even include an "event.target"-like property which makes matters worse...
So, i was wondering how can i work around this one??
I was thinking using #HostListeners, but wouldn't the code executed in that directive get mixed up with the Resizable directive(the module is declared as a directive)??
And messing around with the Resizable-module files doesn't sound like a good idea, since if anyone wants to use my module will have to download my tampered files of the resizable project...
Answer to your question is :
event.preventDefault() will stop the default functionality.
I think this may solve your issue.
The event.preventDefault() method stops the default action of an element from happening.
For example:
Prevent a submit button from submitting a form
Prevent a link from following the URL
$(document).ready(function(){
$("a").click(function(event){
event.preventDefault();
});
});
Hi I'm developing my view in JS and I'm stuck in binding a click handler for my horizontal layout element. I've tried using Jquery
$("#myHorizontalLayout").bind("click",function(){window.alert()});
Which didn't work then I tried using attachPress with the element which obviously didn't exist. Please help.
Update:
The JS view is the default view of the application.
When on/bind does not work, it could be that the HTML of the control has actually not been created yet at this point in time. But even if you delay the binding, the re-rendering (re-creation of the HTML after changes) would remove your listener, at least when bound on the control itself.
A proper way of doing this is using the generic attachBrowserEvent function available on every control (here: on the layout) which internally handles all the rendering/rerendering stuff, see this example:
http://jsbin.com/hijutunefi/1/edit?html,output
attachBrowserEvent works for any browser event, as it attaches a new browser event listener to the root node of the control. For the most common browser events UI5 does event delegation, so for the "click" event and several others addEventDelegate can also be used, as pointed out by aborjinik.
Alternatively, listening on the <body> level with normal jQuery mechanisms should in general also work.
Which didn't work then I tried using attachPress with the element which obviously didn't exist. Please help.
Does this means that the element on which you are attaching event handler doesn't exists at this point? If this is the case you can hook the handler to some container, upper in the DOM hierarchy which you are sure that exists and filter the click events.
Example:
$("body").on("click", "#myHorizontalLayout", function(){
alert("Hey, you!");
});
As of jQuery 1.7, the .on() method is the preferred method for
attaching event handlers to a document. For earlier versions, the
.bind() method is used for attaching an event handler directly to
elements. Handlers are attached to the currently selected elements in
the jQuery object, so those elements must exist at the point the call
to .bind() occurs.
Reference here
So try replacing bind with on and let me know if it works or not.
I am using backbone and Marionette for my Application.I want to create my own event like If div innerHTML clears the event should be trigger.
html code :
<div id="firstDiv"></div>
While div clearing time, I want to unbind the view events.In my Application,we have Main button to go main page.This button common in every screen,So I written a code for this button,once use click on this button div clears and main page will be render,the problem was still the previous view events are listening.So How can I clear the previous View events.
can anyone help me.
Thanks.
You can't really do that, to the best of my knowledge. You can listen to events triggered by the browser (click, focus, etc.), and you can also listen or trigger events on Backbone/Marionette instances.
What you'd like to do is trigger an event on a specific DOM change, which you can't do. Instead, when you clear that div, you should trigger an event yourself and have your code listen for that event.
It is correct to say that you should be thinking about how you can capture this within the view logic, rather than listening to a DOM change.
That said, if you do think that listening to the DOM is the best approach, then this could be a good use case for a Mutation Observer. Explaining how is a little above and beyond, but there is a lot of good information on the MDN Dev page
If you are using Marionette, you should not worry about the previous view events. It automatically handles the Garbage Collections and unbinds the previous events. You have to make the application in such a style that when you render a View in a Region, it will bind the events to that, and when you click on button "Main" you should handle route to get the new View/stored View to be shown in that same region. So the old view will be removed automatically and the events will be also removed. Marionette is best at that part.
And if you have custom events using jQuery I suggest
Use Marionette View's events
OR
Use $(element).off("event").on("event", function(){});