I'm new to Backbone. I have a collection whose url function depends on the textfield text. How do i get that text from my textfield. No i don't want to use JQuery selectors as accessing outside selectors from your views aint a good practice. My HTML stucture is like:
<div id='outer'>
<input type='text' id='xyz'>
<div id='image123'></div>
<div id='div1'>
<ul>
</ul>
</div>
<div id='div2'></div>
</div>
So i got 2 views, 1 collections & 1 model.
How do i get the input text in the collection without using JQuery selectors from my 'outer' view.
[Post updated with View code]
var outerView = Backbone.View.extend
({
el: '#outer',
initialize: function()
{
},
events:
{
'keyup #xyz' : 'keyfunc'
},
keyfunc: function()
{
// inputtext is a global variable & i don't want it that way
inputtext = $('#xyz').val();
},
render: function()
{
},
});
I couldn't figure what are you doing , but only if you want to send the value to the collection with out making it global and changing the url ,try doing it this way
var outerView = Backbone.View.extend
({
el: '#outer',
initialize: function()
{
},
events:
{
'keyup #xyz' : 'keyfunc'
},
keyfunc: function()
{
// not global now
var inputtext = $('#xyz').val();
var myclooction = new MainCollection({
text : inputtext
});
},
render: function()
{
},
});
in the collection
var MainCollection = Backbone.extend.collection({
url : function(){
return "someurl/"+this.text;
},
// receive the value here
initialize:function(options){
this.text = options.text;
}
});
Backbone passes information about the element that triggered the event, and there you can find that value like so:
keyfunc: function(e)
{
inputtext = $(e.currentTarget).val();
this.model.trigger('textChanged', {id: this.myID, data: inputtext});
}
and your model can listen that event like this in its initialize-function.
this.listenTo(this, 'textChanged', this.textChangedHandler);
And the model can then decide what to do with that event. For example to send it to that another view by another event.
textChangeHandler: function (e) {
this.trigger('someTextChanged', e);
}
And your views or collections can listen that event in their initialize-function:
this.listenTo(this.model, 'someTextChanged', this.textChangedHandler);
And the handler would be something like:
textChangeHandler: function (e) {
if (e.id !== this.myID) {
//do stuff
}
}
Related
I have menu item that when clicked should open a chat panel that is part of a different view. Today is my first day looking at Backbone and im a little confused about how I can call a function on another view when the element is clicked.
I want something like the following on the menu view:
view.$('.wkVideoCall')
.on('click', function() {
wk.ui.videoChat.videoChatView.open();
wk.ui.videoChat.openForChatUser();
});
Where wk.ui.videoChat.videoChatView is the view that has .open() as a method.
But the above is not working, is there an event like change but for click that i can be using? or some sort of bind function?
I know have something like this:
View 1 (menu):
events: {
'click .wkVideoCall': 'openVideoChat'
},
openVideoChat: function() {
var videoChatView = new wk.ui.videoChat.videoChatView();
videoChatView.render();
},
View 2 (chat panel):
(function() {
var self = wk.ui.videoChat;
self.VideoChatView = Backbone.View.extend({
el: function() {
[0];
return wk.core.template.load('videoChatTmpl')[0];
},
initialize: function() {
var view = this,
model = view.model,
$el = view.$el;
.on('myEvent', this.open);
view.$('.wkUnmuteVideo')
.on('click', function() {
self.muteMyVideo(false);
});
view.$('.wkUnmuteAudio')
.on('click', function() {
self.muteMyAudio(false);
});
view.$('.wkOptions')
.on('focus', function() {
self.showOptions();
})
.on('blur', function() {
self.hideOptions();
});
},
render: function() {
console.log("inside");
}
});
})();
But nothing is logged
Basically when you click you should render or re-render a subview as muistooshort wrote.
This is a pretty good example : http://todomvc.com/examples/backbone/
And there is plenty of tutorials out there.
I'm novice in Backbone.
I want to show a stock list, where user can open up any stock right from the list and change stock values. After that the whole list should refresh to show changed values.
So as I found out it's better not only to create collection but create collection and a list of stock models.
For this I created a stock collection view for main table and stock model view for adding rows to the table where each row is a single model.
So this is a collection view:
App.Views.StockTable = Backbone.View.extend({
...
initialize: function() {
this.render();
},
render: function() {
this.$el.html(this.template(this.collection));
this.addAll();
return this;
},
addOne: function(stock) {
var row = new App.Views.StockRow({
model: stock,
suppliers: this.suppliers
});
return this;
},
addAll: function() {
var suppliers = new App.Collections.Suppliers();
var that = this;
suppliers.fetch({
success: function() {
_.each(that.collection.toJSON(), that.addOne, that);
}
});
return this;
}
});
And this is my stock row view:
App.Views.StockRow = Backbone.View.extend({
el: 'tbody',
templateRow: _.template($('#stockRow').html()),
templatePreview: _.template($('#stockPreview').html()),
events: {
'click #open': 'open'
...
},
initialize: function() {
this.render();
},
render: function() {
this.$el.append(this.templateRow(this.model))
.append(this.templatePreview({
stock: this.model,
suppliers: this.suppliers
}));
return this;
},
open: function(e) {
var element = $(e.currentTarget);
element.attr('id', 'hide');
$('#stock' + element.data('id')).slideToggle('fast');
}
...
});
I wrote just a piece of code. The problem is that when I click on '#open' that event triggers many times (right the quantity elements in the collection). So when I catch e.currentTarget there are many similar objects.
What i do wrong?
I suspect you have multiple things going on here.
Without seeing your template, I suspect each of your StockRow rows are rendering a tag with the id="open". Since id values should be unique, use a class in your link (example: class="open"), and then reference that class in your click handler:
events: {
'click .open': 'open'
}
Next, since each instance of the StockRow already has a model instance associated with it, just use this.model instead of trying to look it up out of the data attribute of the currentTarget.
open: function () {
$('#stock' + this.model.id).slideToggle('fast');
}
But again, instead of using an id="stock" attribute in your template, use a class… say class="stock-preview". Then just look for that in your open…
open: function () {
this.$el.find('.stock-preview').slideToggle('fast');
}
The other piece that looks questionable to me is the call to this.addAll(); in the render method of the StockTable view. It is best practice to just have your render method render state, instead of having it trigger an ajax call to fetch the state.
For example, in your initialize you can setup some event handlers that react to your collection changing state (below is an incomplete example, just hoping to get you going in the right direction):
initialize: function (options) {
…
_.bindAll(this, 'render', 'renderRow');
this.collection.on('add', this.renderRow);
this.collection.on('reset', this.render);
},
render: function () {
this.$el.html(this.tableTemplateWithEmptyTBodyTags());
this.collection.each(this.renderRow);
return this;
},
renderRow: function () {
var row = new App.Views.StockRow({
model: stock,
suppliers: this.suppliers
});
this.$el.find('tbody').append(row.render().el);
return this;
}
And then outside the table view, you can do a suppliers.fetch(). Which when the response comes back should trigger the reset.
I am trying to create simple Backbone example but i don't understand what is the problem with my code. Why is there 2 testAttr attributes(on directly on object and one in attributes object) and why isn't change event triggering on any of the changes? Also i don't understand what is the correct way to set attributes on model?
Heres my code:
<div id="note"></div>
<script>
var NoteModel = Backbone.Model.extend({
defaults: function() {
return {
testAttr: "Default testAttr"
}
}
});
var NoteView = Backbone.View.extend({
initialize : function() {
this.listenTo(this.model, "change", this.changed());
},
el: "#note",
changed: function () {
debugger;
console.log("change triggered");
this.render();
},
render : function() {
this.$el.html("<h1>" + this.model.get("testAttr") + "</h1>");
return this;
}
});
var note = new NoteModel();
var noteView = new NoteView({model: note});
noteView.render();
note.set("testAttr", "blah1");
note.testAttr = "blah2";
</script>
Change this line:
this.listenTo(this.model, "change", this.changed());
to this:
this.listenTo(this.model, "change", this.changed);
For the second part of your question, using .set() goes through a set of dirty-checking to see if you changed, deleted or didn't change anything, then triggers the appropriate event. It isn't setting the object.property value, it's setting the object.attributes.property value (tracked by backbone.js). If you directly change the object property, there's nothing to initiate that event for you.
...unless of course you use the AMAZING AND TALENTED Object.observe()!!!1! - ITS ALIVE (in Chrome >36)
I'm working with an API and Backbone.js at the moment.
I have two views, both render to the same document element #viewContainer. Both of these views render a table with a couple strings to decribe them and a button that opens a form in a modal.
View 1
App.Views.TaskList = Backbone.View.extend({
el: "#viewContainer",
tagName: 'tr',
events: {
"click button": "showTaskForm"
},
showTaskForm: function (event) {
event.preventDefault();
var id = $(event.currentTarget).data("id");
var item = this.collection.get(id);
var formView = new App.Views.Form({
model: item
});
formView.render();
},
render: function () {
changeActive($('#tasksLink'));
var template = _.template($("#taskList").html(), {});
$('#viewContainer').html(template);
// loop and render individual tasks.
this.collection.each(function (model) {
var variables = {
name: model.get('name'),
button: model.getButton()
};
var template = _.template($("#task").html(), variables);
$("#taskTable tbody").append(template);
});
},
collection: App.Collections.Tasks,
});
View 2
App.Views.ProcessList = Backbone.View.extend({
el: "#viewContainer",
tagName: 'tr',
events: {
"click button": "showStartForm"
},
showStartForm: function (event) {
event.preventDefault();
var id = $(event.currentTarget).data("id");
var item = this.collection.get(id);
var formView = new App.Views.Form({
model: item
});
formView.render();
},
collection: App.Collections.Processes,
render: function () {
changeActive($('#processLink'));
var template = _.template($("#processList").html(), {});
$('#viewContainer').html(template);
this.collection.each(function (model) {
var variables = {
processId: model.get('id'),
processName: model.get('name'),
button: model.getButton()
};
var template = _.template($('#process').html(), variables);
$('#processList tbody').append(template);
});
} });
Neither of these views are rendered by default, both need to be activated by a button on the page and they over-write each other in the DOM. However, which ever view is rendered first, the click event of the buttons in that view are the ones that are always fired.
If there is any more information needed from me let me know and I will edit the question.
Be sure to call undelegateEvents() in the first view when you render the second.
Since you're listening for events on the same elements, essentially you attached two listeners for click events on the same button, and when you change your views you are not cleaning up these listeners.
Here's an article that talks about managing events on view change, which should be really helpful to you.
http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/
As other posters have pointed out, you need to watch out for 'zombie' views (i.e. making sure you undelegate events). If you're building even a moderately complex app, you'll want something that can scale. I find this pattern useful:
var BaseView = Backbone.View.extend({
render: function () {
this.$el.html(this.template());
return this;
},
close: function () {
if (this.onClose) this.onClose();
this.undelegateEvents();
this.$el.off();
this.$el.remove();
}
});
Then whenever you build a view you can do:
var view = BaseView.extend({
//your code
//now the .close() method is available whenever you need to close
//a view (no more zombies!).
});
I have 2 textfields with id's source,destination. If any field value changes that corresponding model attribute will be change. I did this one using Backbone.Model and events object in Marionette.CompositeView. It's working fine.
Once any model Attribute change corresponding function will call. For this I written the following code. It's not working the problem was even one attribute changes both functions are evaluating.
model Code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
Marionette.CompositeView code:
var mapView = Marionette.CompositeView.extend({
events: {
"blur #source": "sAttributeSetting",
"blur #destination": "dAttributeSetting"
},
dAttributeSetting: function() {
this.model.set({"endPlace": document.getElementById(this.ui.destinationPlace).value});
},
sAttributeSetting: function() {
this.model.set({"startPlace": document.getElementById(this.ui.sourcePlace).value});
},
modelEvents: {
"change startPlace": "startMarkerDisplay",
"change endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function() {
alert("start");
},
endingMarkerDisplay: function() {
alert("end");
}
});
html code:
<input type="text" id="source">
<input type="text" id="destination">
creating instance for both model and view
mapModelObj = new mapModel();
var mapViewObj = new mapView({el:$('#mapDiv'), model:mapModelObj});
problems:
Initially If I enter any value in first field(source) getting 2 alert boxes("start", "end").
Initially If you enter any value in second field(destination) getting 4 alert boxes("start", "end", "start", "end")
I tried alot but I didn't understand where I am getting the problem
Can anyone help me.
Thanks
modelEvents should be connected by :. Say, event of changing startPlace should be
'change:startPlace'
If you use space you'll end with two events, not one event specific to this attribute.
Your code 'change startPlace' represents two events, one is 'change', the other is 'startPlace'. So you'll see "start","end","start","end"
My observations are the following for your solution (however I propose a second solution at the bottom):
The binding of entity event has colon syntax. It should be a hash of { "event:name": "eventHandler" } configuration. Multiple handlers can be separated by a space. A function can be supplied instead of a string handler name.
You can use advantage of the el property of the backbone view.
Instead of using document.getElementById(this.ui.sourcePlace), you can use this.$('#source'). This latest searches only in the context of el rather than searching the whole dom. This way the evaluation will be way faster... That way you should use this expression: this.$('.destination').val()
Please check my jsfiddle about your issue: http://jsfiddle.net/orbanbotond/VEcK6/
The code is the following:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "",
endPlace: ""
}
});
var mapView = Marionette.CompositeView.extend({
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
dAttributeSetting: function(){
console.log('end blured');
console.log('input value:' + this.$('.destination').val());
this.model.set({
"endPlace": this.$('.destination').val()
});
console.log('endplace set to: ' + this.model.get('endPlace'));
},
sAttributeSetting: function() {
console.log('start blured');
console.log('input value:' + this.$('.source').val());
this.model.set({
"startPlace": this.$('.source').val()
});
console.log('startPlace set to: ' + this.model.get('startPlace'));
},
modelEvents: {
"change:startPlace": "startMarkerDisplay",
"change:endPlace": "endingMarkerDisplay"
},
startMarkerDisplay: function () {
alert("start");
},
endingMarkerDisplay: function () {
alert("end");
}
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
});
My proposed second solution:
Use the stickit library which does all you are doing. You only need to define the mapping between the dom selector and the observed model attribute.
Here is the jsfiddle for it: http://jsfiddle.net/orbanbotond/fm64P/
Here is the code:
var mapModel = Backbone.Model.extend({
defaults: {
startPlace: "initialStartPlace",
endPlace: "initialEndplace"
},
});
var mapView = Marionette.CompositeView.extend({
template: "#mapDiv",
events: {
"blur .source": "sAttributeSetting",
"blur .destination": "dAttributeSetting"
},
bindings: {
'.source': {
observe: 'startPlace'
},
'.destination': {
observe: 'endPlace'
}
},
onRender: function() {
this.stickit();
console.debug("Sticked to it already");
},
});
$(document).ready(function(){
var mapModelObj = new mapModel();
var mapViewObj = new mapView({
el: $('#mapDiv'),
model: mapModelObj
});
mapViewObj.render();
mapModelObj.bind('change:startPlace', function(obj){alert("New value: " + obj.get('startPlace'));});
mapModelObj.bind('change:endPlace', function(){alert("New value: " + obj.get('endPlace'));});
});
For every code sample I used this template (I used class selectors instead of id selectors):
<div id="mapDiv">
<input type="text" class="source">
<input type="text" class="destination">
</div>