my problem is to bind a model, that I istanciate at certain time during my application, to a View that has been created at initialization time. Let me explain better:
In my route I istanciate a View:
...
var view = new app.FirstView();
view.render();
In
app.FirstView = Backbone.View.extend({
initialize: function(){
...
this.SidebarView = new app.SidebarView();
}
... });
At certain time X during my application I have this behaviour in an another view:
app.AnotherView = Backbone.View.extend({
...
onClickHandler: function(){
var aModel = app.aModel();
var view = app.aView({ model: aModel });
}
})
My problem now, is to bind aModel defined in the onClickHandler to SidebarView which I created and bounded to the FirstView at initialization time.
Please tell me if something is not clear.
Thanks in advance
one solution is to use an event for example,
In your AnotherView trigger a event and pass to model to View
onClickHandler: function(){
var aModel = app.aModel();
Backbone.trigger('model:assigned',aModel);
}
in your sidebarView's initialize you should listen for event
Backbone.on("model:assigned",function(passedModel){
this.model = passedModel;
})
Note1: You must be sure that sidebarView is initialized by the time you are triggering event.
Note2: Try avoiding Global events , i used as i don't know your code structure.
Related
The "change" event is not firing in the following code.
var PageView = Backbone.View.extend({
el: $("body"),
initialize: function(){
this.model.on("change:loading", this.loader, this);
},
loader: function(){
if(this.model.get("loading")){
this.$el.find('.loader').fadeIn(700);
}
else
this.$el.find('.loader').fadeOut(700);
},
});
var PageModel = Backbone.Model.extend({
defaults: {
loading: null,
},
initialize: function(){
this.set({loading:false});
},
});
$(function(){
var pageModel = new PageModel({});
var pageView = new PageView({model: pageModel});
})
It works if I'm adding this in the model's initialize function:
setTimeout(function() {
this.set({'loading': 'false'});
}, 0);
I can leave it this way, but this is a bug.
The situation explained
Here's the order the code runs:
the model is created,
model's initialize function is called, setting the loading attribute to false,
then the model is passed to the view,
then a listener is registered for the "change:loading"
The event handler is never called because the event never occurs after it was registered.
Quick fix
First remove the set from the model.
var PageModel = Backbone.Model.extend({
defaults: {
loading: null
}
});
Then, after creating the view, set the loading attribute.
var pageModel = new PageModel();
var pageView = new PageView({ model: pageModel });
pageModel.set('loading', false); // now the event should trigger
Since the listener is now registered before the model's loading attribute is changed, the event handler will be called.
Optimized solution
Use Backbone's best practices:
Favor .listenTo over .on to avoid memory leaks
Cache jQuery objects
Try to avoid setting the el property on the view
A view is an atomic component that should only care about itself and its sub-views.
While in your case, it wouldn't matter much that you use the el property on the view, it still goes beyond the responsibilities of the view. Let the calling code deal with passing the element to use for this view.
var PageView = Backbone.View.extend({
initialize: function() {
this.model = new PageModel();
this.$loader = this.$('.loader');
this.listenTo(this.model, "change:loading", this.loader);
},
loader: function() {
this.$loader[this.model.get("loading")? 'fadeIn': 'fadeOut'](700);
},
render: function() {
this.loader();
return this;
}
});
Put the defaults where they belong.
var PageModel = Backbone.Model.extend({
defaults: {
loading: false
}
});
Here we choose the body as the element to use for the view, using the el option, and then call render when ready.
$(function() {
var pageView = new PageView({ el: 'body' }).render();
});
The event won't be triggered by the listener right away, instead, we use the render function to put the view in its default state. Then, any subsequent changes of the loading attribute will trigger the callback.
I have listed the most useful answers I've written about Backbone on my profile page. You should take a look, it goes from the beginning to advanced and even provides some clever Backbone components that solves common problems (like detecting a click outside a view).
I am bit new to knockout and jquery mobile, There was a question which is already answered, I need to optimize the PageStateManager class to use generic bindings, currently PageStateManager can only use for one binding,I would really appreciate if someone can guide me to create a generic class to manage page states with knockout bindings Heere is the working code,http://jsfiddle.net/Hpyca/14/
PageStateManager = (function () {
var viewModel = {
selectedHospital: ko.observable()
};
var changePage = function (url, viewModel) {
console.log(">>>>>>>>" + viewModel.id());
$.mobile.changePage(url, {viewModel: viewModel});
};
var initPage = function(page, newViewModel) {
viewModel.selectedHospital(newViewModel);
};
var onPageChange = function (e, info) {
initPage(info.toPage, info.options.viewModel);
};
$(document).bind("pagechange", onPageChange);
ko.applyBindings(viewModel, document.getElementById('detailsView'));
return {
changePage: changePage,
initPage: initPage
};
})();
Html
<div data-role="page" data-theme="a" id="dashBoardPage" data-viewModel="dashBoardViewModel">
<button type="button" data-bind="click: goToList">DashBoard!</button>
</div>
New dashboard model
var dashBoardViewModel = function() {
var self = this;
self.userName = ko.observable('Welcome! ' + "UserName");
self.appOnline = ko.observable(true);
self.goToList = function(){
//I would like to use PageStateManager here
// PageStateManager.changePage($("#firstPage"),viewModel);
ko.applyBindings(viewModel,document.getElementById("firstPage"));//If I click Dashbord button multiple times it throws and multiple bind exception
$.mobile.changePage($("#firstPage"));
}
}
ko.applyBindings(dashBoardViewModel,document.getElementById("dashBoardPage"));
update url : http://jsfiddle.net/Hpyca/14/
Thank you in advance
I would probably go for creating a NavigationService which only handles changing the page and let knockout and my view models handle the state of the pages.
An simple example of such a NavigationService could be:
function NavigationService(){
var self = this;
self.navigateTo = function(pageId){
$.mobile.changePage($('#' + pageId));
};
}
You could then, in your view models just call it when you want it to navigate to a new page. One example would be upon selection of a hospital (which could be done either via a selection function or by manually subscribing to changes to the selectedHospital observable):
self.selectHospital = function(hospital){
self.selectedHospital(hospital);
navigationService.navigateTo('detailsView');
};
Other than the call to the navigationService to navigate, it's just ordinary knockout to keep track of which viewmodel should be bound where. A lot easier than having jquery mobile keeping track of which viewmodel goes where, if you ask me.
I have updated your jsfiddle to show a sample of how this could be done, making as few changes as possible to the HTML code. You can find the updated fiddle at http://jsfiddle.net/Hpyca/15/
I'm trying to add an event to a collection. I want to re-render the view every time the collection changes (new model, model attribute changes, etc). Here is my code:
var app = {}; // custom name space
// models
app.Group = Backbone.Model.extend({
url: '/group'
});
app.Category = Backbone.Model.extend({
url: '/category'
});
// collections
app.GroupList = Backbone.Collection.extend({
model: app.Group,
url: 'data/getGroups.php' // '/groups'
});
app.CategoryList = Backbone.Collection.extend({
model: app.Category,
url: 'data/getCategories.php' // '/categories'
});
app.groupList = new app.GroupList();
app.categoryList = new app.CategoryList();
// views
app.CategoriesView = Backbone.View.extend({
...
});
// bind events
app.GroupList.on('change reset add remove', app.CategoriesView.render);
app.CategoryList.on('change reset add remove', app.CategoriesView.render);
...but, I get the following error in the console "TypeError: app.GroupList.on is not a function". I've tried doing the same for the model instead of the collection but same error - ".on" is not a function. In the documentation it seems "on" belongs to models at least but as mentioned this didn't work either. How is the correct way to add a listener? should I be doing so on the model or the collection?
If anyone can offer any help it would be much appreciated. Thanks
You can only bind events to instances:
app.groupList.on(...);
In the same vein, render is only for a view instance:
// views
app.CategoriesView = Backbone.View.extend({
...
});
var myView = new app.CategoriesView({
// somewhere in here you'll pass app.grouplist...
});
app.groupList.on('change reset add remove', myView.render);
i'm playing with Marionette first time.
After re-rendering ItemViews, their events not triggered.
Simple example:
App = new Marionette.Application;
App.addRegions({
headerRegion: '#header',
contentRegion: '#content',
});
App.addInitializer(function () {
this.Views = {
MainMenu : new MainMenuView(),
ContentOne : new ContentOneView(),
ContentTwo : new ContentTwoView(),
};
});
App.addInitializer(function () {
var self = this;
var eva = self.vent;
eva.listenTo(self.Views.MainMenu, 'content1', function () {
self.contentRegion.show(self.Views.ContentOne);
});
eva.listenTo(self.Views.MainMenu, 'content2', function () {
self.contentRegion.show(self.Views.ContentTwo);
});
});
App.on('start', function () {
var self = this;
self.contentRegion.show(self.View.ContentOne);
});
App.start();
After re-rendering ContentOneView & ContentTwoView, their events not triggered.
What i'm doing wrong?
The problem you are having is that region.show() is going to close any view that is currently occupying that region. Doing so undelegates the view's events. After the initial region.show() you should manually call render on the view.
You can see this explained here and an issue discussing it here.
I managed to solve this problem by using delegating the events when the view is shown in the layout:
layoutView.content.show(contentView);
contentView.delegateEvents();
Although this is only necessary after the first render as mentioned by Andrew Hubbs
instead of using eva to listen to events that happen on the views, try listening to eva for events passed by other views
App.addInitializer(function () {
var eva = self.vent;
var self = this;
this.listenTo(eva, 'someStringHere', function(){/*do stuff here*/};
});
and then in your views you can trigger events through eva/vent
var eva = self.vent;
eva.trigger("someStringHere");
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 :)