I have a simple app, that triggers a boolean and sets a task to completed:
But I want to be able use a "Complete All" Button and set every task to complete. This here works fine:
completeAll: function() {
this.tasks.forEach(function(task) {
task.completed = true;
});
},
http://codepen.io/anon/pen/avzMYr
But instead of setting it directly, I would like to use a method that is called like this, because I have a lot of other code that needs to be separated.
completeTask: function(task) {
task.completed = true;
},
completeAll: function() {
this.tasks.forEach(function(task) {
this.completeTask(task);
});
},
Yet this does not work, see here:
http://codepen.io/anon/pen/EVaMLJ
Any idea how to call the "completeTask(task)" method inside of the completeAll method?
Your problem is that the value of this inside the .forEach() callback is not the same as what it is outside. You can save the outer value of this and then use that saved version to get what you want:
completeAll: function() {
var self = this;
this.tasks.forEach(function(task) {
self.completeTask(task);
});
},
You could use Bind for setting the this value in methods like this:
completeAll: function() {
this.tasks.forEach(function(task) {
this.completeTask(task);
}.bind(this));
}
Related
I'm new to meteor and I'm trying to get a hang of the whole reactivity thing.
There isn't a specifc reason why I want this function to re-run, in fact, it not re-running is actually the desired behavior for my use case. I just want to know why this is happening so I can better understand the concepts.
If I add a function as a property on a template instance, like this:
Template.services.onCreated( function() {
this.templates = [
"web_design",
"painting",
"gardening"
];
this.current_index = new ReactiveVar(0);
this.determineSlideDirection = function() {
console.log(this.current_index.get());
};
});
And then I update the reactive var in response to some event.
Template.services.events({
'click .nav-slider .slider-item': function(event, template) {
var new_selection = event.currentTarget;
template.current_index.set($(new_selection).index());
}
});
The function is not re-run upon the invocation of the set() call.
However, If I have a helper that utilizes the variable, it will be re-run.
Template.services.helpers({
currentTemplate: function() {
var self = Template.instance();
return self.templates[self.current_index.get()];
}
});
Why is this?
Reactive data sources only cause some functions to automatically re-run. These functions are:
Tracker.autorun
Template.myTemplate.helpers({})
Blaze.render and Blaze.renderWithData
In your code above you would want to use Tracker.autorun
Template.services.onCreated( function() {
this.templates = [
"web_design",
"painting",
"gardening"
];
this.current_index = new ReactiveVar(0);
Tracker.autorun(function(){
// actually, this might not work because the context of
// 'this' might be changed when inside of Tracker.
this.determineSlideDirection = function() {
console.log(this.current_index.get());
};
});
});
I guess that's the simple question. I'm new in js, especially in Backbone.js.
All I want to know is how I can refer to my function inside jquery function.
getLanguages: function() {
...
return languages;
},
render: function() {
...
$("input[type='checkbox']").bind("change", function() {
// todo: getLanguages
});
}
I tried to get languages via this but, of course, I got checkbox in this case.
Edit:
It's so simple. Many thanks to all!!!
This is a classic problem in Javascript. You need to reference this inside a callback, but this changes to the element being bound to. A cheap way to do it:
render: function() {
var that = this;
$("input[type='checkbox']").bind("change", function() {
that.getLanguages();
});
}
that will stay defined as the this that render is defined on.
However, you’re using Backbone, and it has more suitable ways to handle this situation. I don’t know the name of your Backbone.View class, but here’s an example adapted from the documentation:
var DocumentView = Backbone.View.extend({
events: {
"change input[type='checkbox']": "doSomething"
},
doSomething: function() {
this.getLanguages(); # uses the correct this
}
});
Calling bind inside render is not The Backbone Way. Backbone views are made to handle event delegation without the unfortunate need to pass this around.
Save this object before bind change event in the scope of render function.
render: function() {
var CurrentObj = this;
$("input[type='checkbox']").bind("change", function() {
CurrentObj.getLanguages();
});
}
You can save the appropriate object into a local variable so from the event handler, you can still get to it:
getLanguages: function() {
...
return languages;
},
render: function() {
...
var self = this;
$("input[type='checkbox']").bind("change", function() {
var lang = self.getLanguages();
...
});
}
I've learned that for scope reasons the this keyword inside an event listener, which is embedded in an object, doesn't refer to the global object but rather to the element which triggered the event.
Now, I understand that if I want to fetch a property I can save it to a variable before the event handler is called. But what can I do if I want to manipulate the property's value?
In the following piece of code I am trying to manipulate the drugCount property within the removeDrug event listener.
var Drugs = {
drugs: $("#drugs_table"),
drugRow: $("#drug").html(),
drugCount: 0,
init: function() {
this.addDrugRow();
this.removeDrugRowHandler();
},
addDrugRow: function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
},
removeDrugRowHandler: function() {
drugCount = this.drugCount;
// also a problematic solution, because it only retains the inital drugCount.
// i.e I need a way to access the "live" count from within the event
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
this.drugCount--; // how should I approach this?
}
});
}
}
Try This
var Drugs = function() {
var me = this;
me.drugs = $("#drugs_table");
me.drugRow = $("#drug").html();
me.drugCount = 0;
me.init = function() {
this.addDrugRow();
this.removeDrugRowHandler();
};
me.addDrugRow = function() {
this.drugCount++;
this.drugs.append(this.drugRow.replace(/{{id}}/,this.drugCount));
$(".drugsSelect").select2();
};
me.removeDrugRowHandler= function() {
var drugCount = me.drugCount;
$(document).on("click",".removeDrug",function(){
if (drugCount>0) {
$(this).parents("tr").remove();
me.drugCount--;
}
});
}
}
As it turns out the easy solution is to use the object name instead of the contextual this.
So instead of this.drugCount I used Drugs.drugCount.
However, this solution only works if I am in the context of a single object. If I were to write a "class" (i.e var Drugs = function(){ ... }) this won't work.
In the following backbone scripts, I tried to change a collection in a view click event.
var StudentView = Backbone.View.extend({
initialize: function() {
console.log("create student items view");
this.collection.bind('add',this.render,this);
this.collection.bind('remove',this.render,this);
},
render : function(){
},
events :{
"click ":"select_students"
},
select_students: function(){
this.collection.reset([]);
_.each(students.models, function(m) {
if(m.get('name')=="Daniel"){
this.collection.add(m);
}
});
}
});
var students_view = new StudentView({el:$("#student_table"),collection:selected_students});
I got this error
How should I call "this.collection" in the code?
You should change you select_students to
select_students: function(){
var self = this;
this.collection.reset([]);
_.each(students.models, function(m) {
if(m.get('name')=="Daniel"){
self.collection.add(m);
}
});
}
The problem is that in JavaScript, the this context is lost in inner functions (like the one you pass to _.each) so the general pattern is to save a reference outside of that (self) and then use that in the inner function.
You can avoid using a reference at all, utilizing Backbone's collection filter method.
select_students: function () {
this.collection.reset(students.filter(function (student) {
return student.get('name') == 'Daniel';
});
}
Rather than using underscore's each() function, backbone collections can be iterated on directly, and can take a context to define what the 'this' variable refers to (passed as the second argument to each below).
So the best way to do this is to:
select_students: function(){
this.collection.reset([]);
students.each(function(m) {
if(m.get('name')=="Daniel"){
this.collection.add(m);
}
}, this);
}
Is it possible to change the state of a toggle function? Like:
myDiv.toggle ... function 1 , function 2
I click on the myDiv element, the function 1 executes
I click again, function 2
I click again, function 1
BUT
Change the state
function 1 again
etc.
But I need to be able to change the state from outside the toggle function.
Here is a javascript object that uses closure to track it's state and toggle:
var TOGGLER = function() {
var _state = true;
var _msg = "function1";
var function1 = function() {
_msg = "function1";
}
var function2 = function() {
_msg = "function2";
}
return {
toggle: (function () {
_state = !_state;
if (_state) {
function1();
} else {
function2();
}
return _msg;
})
}
}();
Here is a jsfiddle that shows how to use it to toggle based with the following jquery: http://jsfiddle.net/yjPKH/5/
$(document).ready(function() {
$("#search").click(function() {
var message = TOGGLER.toggle();
$("#state").text(message);
});
});
The toggle function is meant for simple use cases. Changing the state externally is not "simple" anymore.
You cannot easily/safely (it's internal so it may change during minor versions) access the state variable of the toggle function easily as it's stored in the internal dataset of the element.
If you really want to do it, you can try this code though:
$._data(ELEMENT, "lastToggle" + func.guid, 0);
func is the function you passed to .toggle(), so you need to save this function in a variable. Here's a minimal example: http://jsfiddle.net/xqgrP/
However, since inside the function there's a var guid = fn.guid || jQuery.guid++ statement, I somehow think that the devs actually meant to use guid instead of func.guid for the _data key - in that case a minor update is very likely to break things. And after the fix you'd have to iterate over the data set to retrieve the correct key as there is no way to access the guid from outside.