How do I bind react events from a mixin? - javascript

I want to write a simple mixin for a tooltip. I already know how to bind my mixin to DOM events:
componentDidMount: function() {
var el = this.getDOMNode();
el.addEventListener('mouseenter', this.mouseenter, false);
el.addEventListener('mouseleave', this.mouseleave, false);
},
...but I'd like to bind to the React events instead, to take advantage of its consistent event system. How do I do it?

I think you probably want to do this in the render method of the mixing component, by passing in the mixin's mouseenter and mouseleave handlers as props. I think one option might look like this:
var MouseMixin = {
getMouseProps: function() {
return {
onMouseEnter: this.mouseenter,
onMouseLeave: this.mouseleave
}
},
mouseenter: function() {
console.warn('mouseenter', arguments)
},
mouseleave: function() {
console.warn('mouseleave', arguments)
}
};
Then, in addition to mixing this in, you'd need to apply the behavior. In JSX in React 0.12, you can use the ... spread operator to help with this:
var Example = React.createClass({
mixins: [MouseMixin],
render: function() {
return (
<div { ...this.getMouseProps() }>
Hello world
</div>
)
}
});
See a working fiddle here.

Related

Event Delegation: where to put the "if" statement

Javascript newbie here.
Is there a "best practice" for placement of "if" statements in event delegation?
Context
I'm setting up event listeners using vanilla Javascript (I know jQuery etc. would simplify things, but let's stick to vanilla JS): there's an event listener on the parent element that invokes a function when a child is clicked. In our example, that function to-be-invoked lives elsewhere in the code.
Let's say I only want to take action when element with id=child-element is clicked. To do this, I use an "if" statement.
There are two obvious places I can put the if statement:
Within the event listener
Within the function
Question
Is (1) or (2) preferred? If so, why? ("Better memory management", "code is easier to read", etc.)
Example 1
var foo = {
bindEvent: function () {
document.getElementById('clickableElement').addEventListener('click', function (e) {
const clickTarget = e.target.id
if (clickTarget === 'child-element') {
foo.takeAnAction.bind(foo);
foo.takeAnAction();
};
});
},
takeAnAction: function () {
console.log('Click');
},
};
Example 2
var foo = {
bindEvent: function () {
document.getElementById("clickableElement").addEventListener("click",
foo.takeAnAction.bind(foo));
},
takeAnAction: function(e) {
if (e.target.id === "child-element") {
console.log('click');
};
},
};
Thanks!
I would go with option 1. The reason is that you can easily generalise it to handle any event delegation, so it's reusable. Sample:
var foo = {
bindEvent: function (selector, callback) { //accept a selector to filter with
document.getElementById('clickableElement').addEventListener('click', function (e) {
const clickTarget = e.target; //take the element
// check if the original target matches the selector
if (target.matches(selector)) {
takeAnAction.call(foo);
};
});
},
takeAnAction: function () {
console.log('Click');
},
};
foo.bindEvent("#child-element", foo.takeAction);
Now you can produce any amount of delegated event bindings. Adding another delegated binding is as simple as:
foo.bindEvent("#parent-element", foo.takeAction);
foo.bindEvent(".table-of-content", foo.takeAction);
With option 2, you will not need to change the implementation or produce new functions for each case:
/*... */
takeAnAction: function(event) {
if (event.target.id === "child-element") {
console.log('click');
};
},
takeAnActionForParent: function(event) {
if (event.target.id === "parent-element") {
console.log('click');
};
},
takeAnActionOnTableOfContentItems: function(event) {
if (event.target.classList.contains("table-of-content") {
console.log('click');
};
},
If you need to execute the same logic in each case, there is really no need to add a new function for every single case. So, for maintainability point of view, adding the logic in the event listener that would call another function is simpler to manage than producing different functions to be called.

Attach component to dynamically created elements with Twitter Flight

Been looking to figure out how with Twitter Flight can attach to dynamic created elements.
Having the following HTML
<article>Add element</article>
And the following component definition
var Article = flight.component(function () {
this.addElement = function () {
this.$node.parent().append('<article>Add element</article>');
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
Once a new element is created, the click event doesn't fire. Here's the fiddle: http://jsfiddle.net/smxx5/
That's not how you should be using Flight imho.
Each component should be isolated from the rest of the application, therefore you should avoid this.$node.parent()
On the other hand you can interact with descendants.
My suggestion is to create an "Articles manager" component that uses event delegation.
eg.
http://jsfiddle.net/kd75v/
<div class="js-articles">
<article class="js-article-add">Add element</article>
<div/>
and
var ArticlesManager = flight.component(function () {
this.defaultAttrs({
addSelector: '.js-article-add',
articleTpl: '<article class="js-article-add">Add element</article>'
});
this.addArticle = function () {
this.$node.append(this.attr.articleTpl);
};
this.after('initialize', function () {
this.on('click', {
addSelector: this.addArticle
});
});
});
ArticlesManager.attachTo('.js-articles');
Try attaching Article to each new article added:
JSFiddle: http://jsfiddle.net/TrueBlueAussie/smxx5/2/
var Article = flight.component(function () {
this.addElement = function () {
var newArticle = $('<article>Add element</article>');
this.$node.parent().append(newArticle);
Article.attachTo(newArticle);
};
this.after('initialize', function () {
this.on('click', this.addElement);
});
});
Article.attachTo('article');
The Article.attachTo('article'); at the end, that runs once on load, will only attach to existing article elements.
I hit this problem, and worked around is as follows...
Javascript: All thrown together for brevity, but could easily be separated.
(function(){
var TestComponent, LoaderComponent;
TestComponent = flight.component(function() {
this.doSomething = function()
{
console.log('hi there...');
};
this.after('initialize', function() {
this.on('mouseover', this.doSomething);
});
});
LoaderComponent = flight.component(function() {
this.attachComponents = function()
{
TestComponent.attachTo('.test');
};
this.after('initialize', function() {
// Initalise existing components
this.attachComponents();
// New item created, so re-attach components
this.on('newItem:testComponent', this.attachComponents);
});
});
LoaderComponent.attachTo('body');
}());
HTML: Note that one .test node exists. This will be picked up by Flight on initialization (i.e. not dynamic). We then add a second .test node using jQuery and fire off the event that the LoaderComponent is listening on.
<div class="test">
<p>Some sample text.</p>
</div>
<script>
$('body').append('<div class="test"><p>Some other text</p></div>').trigger('newItem:testComponent');
</script>
This is obviously a very contrived example, but should show that it's possible to use Flight with dynamically created elements.
Hope that helped :)

Wrap a JavaScript function reference

I have the following JavaScript code, which works as expected...
/** #jsx React.DOM */
var TreeView = React.createClass({
render: function() {
return <div ref="treeview"></div>;
},
componentDidMount: function() {
console.log(this.props.onChange);
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: this.props.onChange
}
});
}
});
var MyApp = React.createClass({
onChange: function(e) {
console.log(e);
this.setState({key: e});
},
render: function() {
return (
<div>
<TreeView onChange={this.onChange}/>
<GridView />
</div>
);
}
});
However, with the kendo treeview, on selecting a tree node, the whole node is passed. To get at the underlying key, I would need to process the node as follows:
select: function(e) {
var id = this.dataItem(e.node).id;
this.props.onChange(id);
}
However I've obviously not quite got it right since, and here please excuse my noobness, it seems that in the working instance a reference to the function is being used, whereas in the non-working instance, the function is actually being executed... Or something like that: the error message being returned is:
Cannot call method 'onChange' of undefined.
So what would I need to do to be able to reference the function which extracts the key before calling the onChange method? Note that, if my understanding is correct, onChange needs to be executed in the context of the MyApp class so that any child components will get notified on the change.
EDIT: I've tried using partial application but am still not quite there. I've updated the onChange routine to take a function which returns the key from the node
onChange: function(getKey, e) {
this.setState({Key: getKey(e)});
},
But am not sure how to wire it all up.
Your code looks mostly right. I believe your only problem is that the select callback you're passing to the treeview is being called with the wrong scope. Note that you're using this to mean two different things within the function (once for the actual tree view object and the other for the React component). Easiest solution is probably to store a reference to the onChange function like so:
componentDidMount: function() {
var onChange = this.props.onChange;
var tree = $(this.refs.treeview.getDOMNode()).kendoTreeView({
dataSource: ...,
dataTextField: "Name",
select: function(e) {
var id = this.dataItem(e.node).id;
onChange(id);
}
});
}
Hope this helps.

Implement toggle in Backbone.js events

I'd like to implement a reversible animation in Backbone, in the same way we do it in jquery :
$('a.contact').toggle(
function(){
// odd clicks
},
function(){
// even clicks
});
my question is how to do this in backbone's event syntax?
How to do I mimic the function, function setup?
events : {
'click .toggleDiv' : this.doToggle
},
doToggle : function() { ??? }
Backbone's view events delegate directly to jQuery, and give you access to all of the standard DOM event arguments through the callback method. So, you can easily call jQuery's toggle method on the element:
Backbone.View.extend({
events: {
"click a.contact": "linkClicked"
},
linkClicked: function(e){
$(e.currentTarget).toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
}
});
I was looking for a solution to this problem and I just went about it the old fashioned way. I also wanted to be able to locate my hideText() method from other views in my app.
So now I can check the status of the 'showmeState' from any other view and run either hideText() or showText() depending on what I want to do with it. I have tried to simplify the code below by removing things like render and initialize to make the example more clear.
var View = Backbone.View.extend({
events: {
'click': 'toggleContent'
},
showmeState: true,
toggleContent: function(){
if (this.showmeState === false) {
this.showText();
} else {
this.hideText();
}
},
hideText: function() {
this.$el.find('p').hide();
this.showmeState = false;
},
showText: function() {
this.$el.find('p').show();
this.showmeState = true;
}
});
var view = new View();
Is the element you want to toggle within the view receiving the event? If so:
doToggle: function() {
this.$("a.contact").toggle()
}
I actually believe the only to do this using events is to add a trigger in order to keep the actual flow together. It seems a bit clumsy to be honest to have to use toggle in this way.
Backbone.View.extend({
events: {
"click .button": "doToggle"
},
doToggle: function(e){
var myEle = $(e.currentTarget);
$(e.currentTarget).toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
myEle.trigger('click');
}
});
It's probably cleaner to just use
Backbone.View.extend({
el: '#el',
initalize: function() {
this.render();
},
doToggle: {
var myEle = this.$el.find('.myEle');
myEle.toggle(
function() {
// odd clicks
},
function() {
// even clicks
}
);
},
render: function(e){
//other stuff
this.doToggle();
return this;
}
});

jQuery: UI widget definition

I have a widget defined like so:
$.widget("ui.mywidget", {
_init: function() {
this.element.bind("keyup", function(event) {
alert(this.options);
alert(this.options.timeout);
});
}
});
And trying to call it like so:
$("input.mywidget").mywidget({timeout: 5});
I also redefined the bind method using the this.element.keyup(function(event) { ... }) style: no difference.
But, in the keyup bind, this.options (and referencing it just as options) both yield undefined. I thought the UI widget framework allowed this type of abstraction; am I doing something wrong?
When inside bind(), this changes to refer to the object that the event is raised on. Try:
$.widget("ui.mywidget", {
_init: function(options) {
var opts = this.options;
this.element.bind("keyup", function(event) {
alert(opts);
alert(opts.timeout);
});
}
});
What #Dave said is right. You can also set "this" to a variable rather than using options as an argument to the init function. Here is how I see it implemented often:
$.widget("ui.mywidget", {
options: {
timeout: 100
},
_init: function() {
var self = this;
self.element.bind("keyup", function(event) {
alert(self.options);
alert(self.options.timeout);
});
}
});
Why stop there? Check out $.proxy and write better code
$.widget("ui.mywidget", {
_create: function() {
//Under this syntax, _omgAlertsRule must be a method of 'this'
this.element.bind("keyup", $.proxy( this, '_omgAlertsRule' ) );
},
_omgAlertsRule: function( event ){
alert(this.options);
alert(this.options.timeout);
}
});

Categories

Resources