CoffeeScript: How to use both fat arrow and this? - coffeescript

I have a coffeescript class that has some jquery event listeners. I would like to use the fat arrow => to avoid having to reference the class, but I still need a reference to the element that would usually be used with this. How can I use both?
class PostForm
constructor: ->
$('ul.tabs li').on 'click', =>
tab = $(this)
#highlight_tab(tab)
#set_post_type(tab.attr('data-id'))
highlight_tab: (tab)->
tab.addClass 'active'
set_post_type: (id) ->
$('#post_type_id').val(id)

CoffeeScript links both this and # to the outer context, therefore you cannot access the context that jQuery provided (aka the desired "this"). Use event.target instead:
class PostForm
constructor: ->
$('ul.tabs li').on 'click', (event) =>
tab = $(event.target)
#highlight_tab(tab)

Using evt.currentTarget
You should probably use evt.currentTarget (which is equivalent to this) instead of evt.target (which isn't). If the node that you are tapping for click notifications has child nodes, evt.target might be one of those child nodes instead of the node you added the click handler to.
See http://codepen.io/ddopson/pen/erLiv for a demo of this behavior. (click on the inner red box to see that currentTarget points at the red div while target points at outer blue div that the event handler is registered on)
$('ul.tabs li').on 'click', (event) =>
tab = $(event.currentTarget)
#highlight_tab(tab)
Answer to the question asked - getting both `=>` and `this`:
You can do the following...
$('ul.tabs li').on 'click', (event) =>
tab = $(` this `) # MAKE SURE TO ADD THE SPACES AROUND `this`
#highlight_tab(tab)
The spaces are critical as they prevent Coffee from munching this into _this.
Using `self` and `->`
Alternatively, do the following ...
self = this
$('ul.tabs li').on 'click', (event) ->
tab = $(this)
self.highlight_tab(tab)
This is similar to CQQL's answer, except that I prefer the idiomatic use of self as the variable name; my VIM syntax highlighting rules color self as a "special" variable just as it would for this, arguments, or prototype.

I prefer this version, because I can understand it more easily.
class PostForm
constructor: ->
post_form = this
$('ul.tabs li').on 'click', (event) ->
tab = $(this)
post_form.highlight_tab(tab)

You may want to access variables set in the constructor from your functions. This would be how you do it (the key is calling the function via self while first extracting this with a thin arrow):
class PostForm
constructor: ->
self = this
#some_contrived_variable = true
$('ul.tabs li').on 'click', ->
tab = $(this)
self.highlight_tab(tab)
self.set_post_type(tab.attr('data-id'))
highlight_tab: (tab) ->
# Because of the fat arrow here you can now access # again
if #some_contrived_variable
tab.addClass 'active'
set_post_type: (id) ->
$('#post_type_id').val(id)
BTW: This is a great explanation of when to use the fat and thin arrow.
Summary:
Do you use this (#) in the function?
Do you want to execute the function later, possibly from a different scope?

Related

Find out if a leaflet control has already been added to the map

I wrote a custom Leaflet control. It's some kind of legend that may be added for each layer. The control itself has a close button to remove it from the map (like a popup).
The control can be added by clicking a button.
My problem is that the user may add the same control to the map several times. So what I need is to test if this specific control has already been added to the map and, if so, don't add it again.
I create a control for each layer, passing some options
var control = L.control.customControl(mylayer);
and add it to my map on button click
control.addTo(map);
Now imagine the control has a close button and may be closed. Now if the user clicks the button again, I only want to add the control if it's not already on the map - something like this (hasControl is pseudocode, there is afaik no such function)
if(!(map.hasControl(control))) {
control.addTo(map);
}
For simplicity I made an example where I create a zoom control and add it twice here.
Easiest way is to check for the existence of the _map property on your control instance:
var customControl = new L.Control.Custom();
console.log(customControl._map); // undefined
map.addControl(customControl);
console.log(customControl._map); // returns map instance
But please keep in mind, when using the _map property, that the _ prefix of the property implies that it's a private property, which you are normally not supposed to use. It could be changed or removed in future versions of Leaflet. You're not going to encounter that if you use the follow approach:
Attaching a reference of your custom control to your L.Map instance:
L.Control.Custom = L.Control.extend({
options: {
position: 'bottomleft'
},
onAdd: function (map) {
// Add reference to map
map.customControl = this;
return L.DomUtil.create('div', 'my-custom-control');
},
onRemove: function (map) {
// Remove reference from map
delete map.customControl;
}
});
Now you can check for the reference on your map instance like so:
if (map.customControl) { ... }
Or create a method and include it in L.Map:
L.Map.include({
hasCustomControl: function () {
return (this.customControl) ? true : false;
}
});
That would work like this:
var customControl = new L.Control.Custom();
map.addControl(customControl);
map.hasCustomControl(); // returns true
map.removeControl(customControl);
map.hasCustomControl(); // returns false
Here's a demo of the concept on Plunker: http://plnkr.co/edit/nH8pZzkB1TzuTk1rnrF0?p=preview

Switch class on tabs with React.js

So I have a tab-component that has 3 items:
React.DOM.ul( className: 'nav navbar-nav',
MenuItem( uid: 'home')
MenuItem( uid: 'about')
MenuItem( uid: 'contact)
)
And in the .render of MenuItem:
React.DOM.li( id : #props.uid, className: #activeClass, onClick: #handleClick,
React.DOM.a( href: "#"+#props.uid, #props.uid)
)
Every time I click an item, a backbone router gets called, which will then call the tab-component, which in turn will call a page-component.
I'm still trying to wrap my head around the fact there's basically a one-way data-flow. And I'm so used to manipulating the DOM directly.
What I want to do, is add the .active class to the tab clicked, and make sure it gets removed from the inactive ones.
I know the CSS trick where you can use a data- attribute and apply different styling to the attribute that is true or false.
The backbone router already has already gotten the variable uid and calls the right page. I'm just not sure how to best toggle the classes between tabs, because only one can be active at the same time.
Now I could keep some record of which tab is and was selected, and toggle them etc. But React.js already has this record-keeping functionality.
The #handleClick you see, I don't even want to use, because the router should tell the tab-component which one to give the className: '.active' And I want to avoid jQuery, because React.js doesn't need direct DOM manipulation.
I've tried some things with #state but I know for sure there is a really elegant way to achieve this fairly simple, I think I watched some presentation or video of someone doing it.
I'm really have to get used to and change my mindset towards thinking React-ively.
Just looking for a best practice way, I could solve it in a really ugly and bulky way, but I like React.js because it's so simple.
Push the state as high up the component hierarchy as possible and work on the immutable props at all levels below. It seems to make sense to store the active tab in your tab-component and to generate the menu items off data (this.props in this case) to reduce code duplication:
Working JSFiddle of the below example + a Backbone Router: http://jsfiddle.net/ssorallen/4G46g/
var TabComponent = React.createClass({
getDefaultProps: function() {
return {
menuItems: [
{uid: 'home'},
{uid: 'about'},
{uid: 'contact'}
]
};
},
getInitialState: function() {
return {
activeMenuItemUid: 'home'
};
},
setActiveMenuItem: function(uid) {
this.setState({activeMenuItemUid: uid});
},
render: function() {
var menuItems = this.props.menuItems.map(function(menuItem) {
return (
MenuItem({
active: (this.state.activeMenuItemUid === menuItem.uid),
key: menuItem.uid,
onSelect: this.setActiveMenuItem,
uid: menuItem.uid
})
);
}.bind(this));
return (
React.DOM.ul({className: 'nav navbar-nav'}, menuItems)
);
}
});
The MenuItem could do very little aside from append a class name and expose a click event:
var MenuItem = React.createClass({
handleClick: function(event) {
event.preventDefault();
this.props.onSelect(this.props.uid);
},
render: function() {
var className = this.props.active ? 'active' : null;
return (
React.DOM.li({className: className},
React.DOM.a({href: "#" + this.props.uid, onClick: this.handleClick})
)
);
}
});
You can try react-router-active-componet - if you working with boostrap navbars.
You could try to push the menu item click handler up to it's parent component. In fact I am trying to do something similar to what you are doing.. I have a top level menubar component that I want to use a menubar model to render the menu bar and items. Other components can contribute to the top level menubar by adding to the menubar model... simply adding the top level menu, the submenuitem, and click handler (which is in the component adding the menu). The top level component would then render the menubar UI and when anything is clicked, it would use the "callback" component click handler to call to. By using a menu model, I can add things like css styles for actice/mouseover/inactive, etc, as well as icons and such. The top level menubar component can then decide how to render the items, including mouse overs, clicks, etc. At least I think it can.. still working on it as I am new to ReactJS myself.

Passing a reference of parent class to child

I'm trying to pass a reference to a parent class instance to its child class. The parent class is:
class UI
constructor: (parameters) ->
#addBTS = new AddBTS
#toolbar = new Toolbar(this)
The AddBts and Toolbar classes are:
class Toolbar
constructor: (#parent) ->
add_bts_clickhandler: () =>
$('body').on 'click', '#add_bts', ->
#parent.addBTS.display()
class AddBTS
constructor: () ->
display: () =>
$("#setBuildingTileSet").show()
console.log 'Showing Sir'
All these classes are defined in separate CoffeeScript files and are joined together before comalation in the following order (Game is where the instance of UI lives):
'src/Game'
'src/UI/UI'
'src/UI/AddBTS'
'src/UI/Toolbar'
No, when I call add_bts_clickhandler: () I get the error message: * Cannot read property 'addBTS' of undefined *
So it thinks that parent is undefined, when it's clearly not.
Please could someone help me, I'm completely stuck as my code seams fine.
My solution:
I call add_bts_clickhandler:
ui = new window.UI();
ui.toolbar.add_bts_clickhandler();
My classes:
class window.UI
constructor: (parameters) ->
#addBTS = new window.AddBTS
#toolbar = new window.Toolbar(this)
class window.AddBTS
constructor: () ->
display: () =>
$("#setBuildingTileSet").show()
console.log 'Showing Sir'
class window.Toolbar
constructor: (#parent) ->
add_bts_clickhandler: () =>
p = #parent ## introduce a new variable!
$('body').on 'click', '#add_bts', ->
p.addBTS.display(); ##
##parent.addBTS.display()
Let us look Toolbar class a bit closer:
class window.Toolbar
constructor: (#parent) ->
add_bts_clickhandler: () =>
$('body').on 'click', '#add_bts', ->
#parent.addBTS.display()
Part of the generated javascript:
Toolbar.prototype.add_bts_clickhandler = function() {
return $('body').on('click', '#add_bts', function() {
return this.parent.addBTS.display();
});
};
In this line: return this.parent.addBTS.display();
this refers to the callback function()! not to the add_bts_clickhandler.
I hope I helped you.

d3.js CoffeeScript class execution context

I am creating a d3.js chart using a CoffeeScript class. I would like to attach a method to a click event, and then run another method depending on what was clicked:
class #Chart
drawChart: ->
...
dataArea
.enter()
.append("path")
.on("click", #onClick);
...
onClick: ->
if d3.select(this).attr("type") == 'video'
#runVideo(d3.select(this).attr("title"))
runVideo: ->
The problem is that in the onClick method the execution context ("this") is the selection and not the Chart class, so "runVideo is not a function." How can I access selection attributes from within the onClick method and also run the runVideo method?
What you want to do is somehow capture the this when you add the click callback.
You have a few options here:
// The Coffeescript way:
.on("click", (args...) => #onClick(args...));
// The jQuery way:
.on("click", $.proxy(#onClick, #))
// The ECMAscript5 way:
.on("click", #onClick.bind(#))
Then you need to fix your onClick to this:
onClick: (evt) ->
if d3.select(evt.target).attr("type") == 'video'
#runVideo(d3.select(evt.target).attr("title"))

Basic CoffeeScript not firing events when run?

I'm having real problems writing a simple Backbone.js app using CoffeeScript and Zepto.js
This is the simplest Backbone view yet the events don't fire. I get no errors in the console either? Where am I going wrong?
#Main view
class AppView extends Backbone.View
constructor: ->
#el = $("#books")
#template = _.template("<div>New Item <a href='' id='addNew'> add new item</a></div>")
events: {
"click" : "createNew"
}
render: =>
#el.html(#template())
createNew : ->
console.log "new"
#Onload
$(document).ready ->
view = new AppView
view.render()
I've been following the only example I can find of CoffeeScript & Backbone together https://github.com/bnolan/Backbone-Mobile/blob/master/application.coffee
However if I add super into my view code above I get an undefined error, his code does not.
The class Backbone.View has its own constructor that does plenty of work, and you are overriding it and not calling super. Bad.
Instead, Backbone.View provides you the ability to define your own constructor-type function called initialize. Perform all your setup there. Backbone.View#constructor will call initialize.
#Main view
class AppView extends Backbone.View
initialize: ->
#el = $("#books")
#template = _.template(
"<div>New Item <a href='' id='addNew'> add new item</a></div>"
)
I had a similar problem (events not firing) and found that the problem was due to not setting #el. I set that:
#el: $("#content")
and it worked.