I'm using require.js with backbone.js to structure my app. In one of my views:
define(['backbone', 'models/message', 'text!templates/message-send.html'], function (Backbone, Message, messageSendTemplate) {
var MessageSendView = Backbone.View.extend({
el: $('#send-message'),
template: _.template(messageSendTemplate),
events: {
"click #send": "sendMessage",
"keypress #field": "sendMessageOnEnter",
},
initialize: function () {
_.bindAll(this,'render', 'sendMessage', 'sendMessageOnEnter');
this.render();
},
render: function () {
this.$el.html(this.template);
this.delegateEvents();
return this;
},
sendMessage: function () {
var Message = Message.extend({
noIoBind: true
});
var attrs = {
message: this.$('#field').val(),
username: this.$('#username').text()
};
var message = new Message(attrs);
message.save();
/*
socket.emit('message:create', {
message: this.$('#field').val(),
username: this.$('#username').text()
});
*/
this.$('#field').val("");
},
sendMessageOnEnter: function (e) {
if (e.keyCode == 13) {
this.sendMessage();
}
}
});
return MessageSendView;
});
When keypress event is triggered by jquery and sendMessage function is called - for some reason Message model is undefined, although when this view is first loaded by require.js it is available. Any hints?
Thanks
Please see my inline comments:
sendMessage: function () {
// first you declare a Message object, default to undefined
// then you refrence to a Message variable from the function scope, which will in turn reference to your Message variable defined in step 1
// then you call extend method of this referenced Message variable which is currently undefined, so you see the point
var Message = Message.extend({
noIoBind: true
});
// to correct, you can rename Message to other name, e.g.
var MessageNoIOBind = Message.extend ...
...
},
My guess is that you've bound sendMessageOnEnter as a keypress event handler somewhere else in your code. By doing this, you will change the context of this upon the bound event handler's function being called. Basically, when you call this.sendMessage(), this is no longer your MessageSendView object, it's more than likely the jQuery element you've bound the keypress event to. Since you're using jQuery, you could more than likely solve this by using $.proxy to bind your sendMessageOnEnter function to the correct context. Something like: (note - this was not tested at all)
var view = new MessageSendView();
$('input').keypress(function() {
$.proxy(view.sendMessageOnEnter, view);
});
I hope this helps, here is a bit more reading for you. Happy coding!
Binding Scopes in JavaScript
$.proxy
Related
In my javascript file, I have defined an app object that takes an initialization function which is triggered upon document ready via JQuery.
$(document).ready(function() {
console.log("JQuery ready");
app.initialize();
});
The app is defined as
var app = {
_GPS_ENABLED: false,
initialize: function() {
var self = this;
// deviceready Event Handler
$(document).on('deviceready', function() {
... ...
// BIND A CLICK EVENT TO A FUNCTION DEFINED IN A LATER STEP
$('#isGPSenabled').on("click", self.isGPSenabled);
... ...
});
},
isGPSenabled: function() {
cordova.plugins.diagnostic.isGpsLocationEnabled(function(enabled) {
// HERE I NEED TO ACCESS THE "APP" ATTRIBUTE "_GPS_ENABLED"
._GPS_ENABLED = enabled; // HOW CAN I ACCESS THE _GPS_ENABLED ATTRIBUTE ON APP
});
}
}
The HTML part has:
<button id = "isGPSenabled">IS GPS ENABLED</button>
How can I access the app's attribute from the function attached to a button?
Previously I've referenced the object by it's name within itself. I think it was a pattern I saw once which worked for my needs at the time. Haven't really thought about the positives or negatives much but it has never caused me any issues in previous work.
Here is an example to to demonstrate:
const app = {
isEnabled: null,
init: () => {
app.isEnabled = false;
},
toggleEnabled: () => {
app.isEnabled = !app.isEnabled;
},
displayEnabled: () => {
console.log('isEnabled?:', app.isEnabled);
}
}
app.displayEnabled(); // null
app.init();
app.displayEnabled(); // false
app.toggleEnabled();
app.displayEnabled(); // true
I am currently in the process of working on the app which is through modular pattern.
The problem i am currently getting is that once the Ajax is complete, i want to be able to fire a function within the object. The object i can see but when i specify a function, it fails and comes back as Undefined.
JS
var TestCase = {
settings: {
cu: $('.select'),
},
init: function() {
se = this.settings;
},
windowsReady: function() {
TestCase.init();
if ($.fn.selectBox) {
TestCase.selectBind();
}
},
ajaxComp: function() {
TestCase.init();
TestCase.selectBind();
},
selectBind: function(){
se.cu.selectBox();
},
};
JS Fire - The selectBind works fine when its loaded through the ready call. However as mentioned before, the ajaxcomplete keeps coming back as Undefined for TestCase.ajaxComp(); or a direct call for TestCase.selectBind(); Please note that when i console.log(TestCase) it lists all the objects.
$(document).ready(function () {
TestCase.windowsReady();
});
$(document).ajaxSuccess(function() {
console.log(TestCase);
TestCase.ajaxComp();
console.log('completed');
});
This is happenning because of this line:
se = this.settings;
The this in the scope of your $(document).ajaxSuccess(...) method will be the jQuery object, not TestCase object.
Try changing it to se = TestCase.settings;
I have a template with helpers and events like:
Template.myTemplate.helpers({
edit: function () {
console.log('helper');
return this.value;
}
});
Template.myTemplate.events({
'click .edit_button': function () {
console.log('click');
// Toggle the value this.value
}
});
Through the logs (simplified here) I can verify that the helper is called when the template is rendered on the page. However, when the event is fired the helper console message is not fired. It as though the event changing the value does not trigger processing of the helper, making the page not very reactive.
I have tried to assign a reactive variable and use it, to no avail.
Template.myTemplate.rendered = function () {
this.value = new ReactiveVar();
}
// Setting and getting using the .get()/.set() methods.
How does one cause the helpers to be reprocessed from an event?
In events you want to set value to ReactiveVar object.
In helpers you simply return that value of this object, helpers behaves like Tracker.autorun, they are reactive.
Template.myTemplate.helpers({
edit: function () {
console.log('helper');
// helpers are wrapped with Tracker.autorun, so any ReactiveVar.get()
// inside this function will cause to rerun it
return Template.instance().value.get()
}
});
Template.myTemplate.events({
'click .edit_button': function (e, tmpl) {
console.log('click');
// here you save value
tmpl.value.set(newValue)
}
});
Template.myTemplate.created = function () {
this.value = new ReactiveVar();
}
I'm trying to manually trigger a click event right after the html has been rendered but it's not working.
To simplify and verify that it's not working I tried this code:
var _testView = Backbone.View.extend({
events : {
'click a' : 'sayHi'
},
initialize : function() {
this.render();
this.$el.find('a').trigger('click');
},
render : function() {
$(document.body).html(
this.$el.html('alert hi')
);
},
sayHi : function() {
alert('Hi');
return false;
}
});
var y = new _testView;
I'm trying to manually trigger the click event but it's not being triggered. If I'm going to put the trigger in a setTimeout with a delay of 500 it will work. I don't know why.... thx
I found the answer. I looked at the Backbone core and I see that initialize method is being called first before attaching the events to the view.
View = Backbone.View = function(options) {
this.cid = _.uniqueId('view');
this._configure(options || {});
this._ensureElement();
this.initialize.apply(this, arguments);
this.delegateEvents();
};
You are calling the click event on the element which haven't been yet created. You should call the function when the render is finished or you can just call this.sayHi() instead of triggering the click.
hjuster is correct. the $(document.body) wait for the 'document ready' event, and you are calling new _testView before the document is ready. You can change your intialize to this - it queues up the trigger to execute after the document is ready.
initialize : function() {
this.render();
var self = this;
$(function(){
self.$el.find('a').trigger('click');
});
},
I added the var 'self' since you can't reference 'this' to get your _testView object in the function.
It works in this fiddle
The 2nd answer to this question nicely explains how event declarations in Backbone.js views are scoped to the view's el element.
It seems like a reasonable use case to want to bind an event to an element outside the scope of el, e.g. a button on a different part of the page.
What is the best way of achieving this?
there is not really a reason you would want to bind to an element outside the view,
there are other methods for that.
that element is most likely in it's own view, (if not, think about giving it a view!)
since it is in it's own view, why don't you just do the binding there, and in the callback Function,
use .trigger(); to trigger an event.
subscribe to that event in your current view, and fire the right code when the event is triggered.
take a look at this example in JSFiddle, http://jsfiddle.net/xsvUJ/2/
this is the code used:
var app = {views: {}};
app.user = Backbone.Model.extend({
defaults: { name: 'Sander' },
promptName: function(){
var newname = prompt("Please may i have your name?:");
this.set({name: newname});
}
});
app.views.user = Backbone.View.extend({
el: '#user',
initialize: function(){
_.bindAll(this, "render", "myEventCatcher", "updateName");
this.model.bind("myEvent", this.myEventCatcher);
this.model.bind("change:name", this.updateName);
this.el = $(this.el);
},
render: function () {
$('h1',this.el).html('Welcome,<span class="name"> </span>');
return this;
},
updateName: function() {
var newname = this.model.get('name');
console.log(this.el, newname);
$('span.name', this.el).text(newname);
},
myEventCatcher: function(e) {
// event is caught, now do something... lets ask the user for it's name and add it in the view...
var color = this.el.hasClass('eventHappened') ? 'black' : 'red';
alert('directly subscribed to a custom event ... changing background color to ' + color);
this.el.toggleClass('eventHappened');
}
});
app.views.sidebar = Backbone.View.extend({
el: '#sidebar',
events: {
"click #fireEvent" : "myClickHandler"
},
initialize: function(){
_.bindAll(this, "myClickHandler");
},
myClickHandler: function(e) {
window.user.trigger("myEvent");
window.user.promptName();
}
});
$(function(){
window.user = new app.user({name: "sander houttekier"});
var userView = new app.views.user({model: window.user}).render();
var sidebarView = new app.views.sidebar({});
});
Update: This answer is no longer valid/right. Please see other answers below!
Why do you want to do this?
Apart from that, you could always just bind it using regular jQuery handlers. E.g.
$("#outside-element").click(this.myViewFunction);
IIRC, Backbone.js just uses the regular jQuery handlers, so you're essentially doing the same thing, but breaking the scope :)