backbone.js - controller properties from a view - javascript

I have a controller property called authenticated which defaults to false. However, in my login view I need to be able to set it to true. Also, in my logout view I need to be able to set it to false. How can I expose this property within the view?
var Controller = Backbone.Controller.extend({
...
authenticated: false,
login: function() {
if(this.authenticated)
{
location.hash = '!/dashboard';
} else {
new LoginView();
}
},
logout: function() {
$.post('/admin/logout', {},
function(resp){
}, "json");
this.authenticated = false;
location.hash = '!/login';
}
...
});

Your controller is correctly doing the login and logout functionality. All you need to do is have your view fire backbone.js events and have the controller be registered to receive those.
Somewhere in your controller, you need something like:
var loginView = new LoginView(...); // params as needed
loginView.bind("login_view:login", this.login);
loginView.bind("login_view:logout", this.logout);
loginView.render();
Also, you need to assure that the controller is set up to handle events, so something like this is needed in your initialize function:
_.extend(this, Backbone.Events);
_.bindAll(this, "login", "logout");
Your view will need the event code, so be sure to add the _.extend(...) call into its initialize.
In your view where appropriate, you need:
this.trigger("login_view:login");
and
this.trigger("login_view:logout");
As a final note, you want the controller to do the login and logout server calls. All you need from the view is an event and potentially a populated model or data otherwise. This data would be passed as a parameter in the trigger statement(s) and would be received as an argument in the login/logout functions. I have not included this in the code, however.
You basically want the view to manage the DOM and bubble up application events to the controller. The controller can then mediate with the server and manage any necessary views.

Related

How to update a tab view from child page in Angular.JS

So I'm trying to update a list view in a parent tab window using Angular.js with the Ionic Framework, but I can't quite seem to figure out how to do it.
Here is the code for the child window:
$scope.currentUsername = Parse.User.current().get("username");
$scope.saveChanges = function saveChanges(user){
var currentUser = Parse.User.current();
var newUserName = user.newUsername;
currentUser.set("username", newUserName);
currentUser.save(null, {
success: function(currentUser){
alert("Changes successfully made!");
}
});
$state.go('tab.more');
}
$scope.cancelChanges = function cancelChanges(){
$state.go('tab.more');
}
And I am trying to take the data once it's sent to Parse back to the parent view but I don't know how to refresh the page after the script is run. In my function cancelChanges() I simply used $state.go to go back to the parent view because why update the view when there is not data to be updated.
Here is the code for my parent window:
$scope.currentUser = Parse.User.current().get("username");
$scope.editProfile = function editProfile(){
$state.go('tab.more-editusername');
}
$scope.logOut = function logOut(){
Parse.User.logOut();
alert("Logout successful!");
$state.go('login');
}
ionic views are cached by default... so I do not believe the update is happening unless you force it to
From Documentation
View LifeCycle and Events Views can be cached, which means controllers normally only load once, which may affect your controller
logic. To know when a view has entered or left, events have been added
that are emitted from the view's scope. These events also contain data
about the view, such as the title and whether the back button should
show. Also contained is transition data, such as the transition type
and direction that will be or was used.
you can try and listen for the $ionicView.enter event and then update the view with the new data

Backbone destroy a model outside of a collection

I have a UserPanel view, which uses a UserModel as its model. In the template for the UserPanel, I have a conditional that checks whether or not the model is undefined. If the model exists, it displays user information. If it doesn't, it displays the "registration" form.
On the user information part of the UserPanel, I have what's essentially an "unregister" button. A user clicks it, and the user information is deleted. The UserPanel responds by re-rendering, allowing them to register a different UserModel.
Common sense tells me to call this.model.destroy. When I use this method, my model is deleted from my data store, but the object still exists in this.model. When the view responds to the model update (by calling render), it still thinks it has a valid model, with all its data and the like. I can call delete on this.model, but that doesn't trigger any events. I can't trigger the event before I delete, because then the view updates before I can delete the model. I have setup the view to respond to model deletions with a method that simply uses delete on the model, then calls render. This works, but bothers me on a conceptual level, since those methods are for handling view updates and not more model manipulation.
In general, what's the proper way to explicitly dispose of a model that is not stored by a collection?
EDIT: I am using Backbone.localStorage as my data store. This might have something to do with it.
Instead of binding to the destroy event I would use the success callback of the model.destroy like so:
this.model.destroy({ success: _.bind(this.onModelDestroySuccess, this) });
Then rename your modelDestroyedView to onModelDestroySuccess:
onModelDestroySuccess: function () {
delete this.model;
this.render();
},
I would also define a cleanupModelEvents method that cleans up your event bindings to the model:
cleanupModelEvents: function() {
this.stopListening(this.model);
},
And call that from onModelDestroySuccess:
onModelDestroySucess: function () {
this.cleanupModelEvents();
delete this.model;
this.render();
},
Hope this helps.
this.model.destroy();
and then on the destroy event
this.model = null;

having issue with backbonejs router

Scenario
I am working on backbone app. What is happening right now is when user clicks edit link on page then it should show a form. I am trying to implement this using backbone routers rather than events. With events object it works perfectly fine. To use routers, I am using global events.
Problem
The problem is that when user clicks on edit link, it shows me following error in console
Uncaught TypeError: Object 10 has no method 'toJSON' views.js:57
This error is because on line 57 in views.js, I am using this.model.toJSON() whereas I am not passing model via router. I don't know how pass model through router
Here is my router. Note: All of the following codes are in separate files
App.Router = Backbone.Router.extend({
routes: {
'contacts/:id/edit': 'editContact'
},
editContact: function (id) {
console.log('yahhhhh');
vent.trigger('contact:edit', id);
}
});
In above router I am triggering an event inside editContact function. Then I am listening to above event in following initialize function.
App.Views.App = Backbone.View.extend({
initialize: function () {
vent.on('contact:edit', this.editContact, this);
},
editContact: function (contact) {
var editContactView = new App.Views.EditContact({ model: contact });
$('#editContact').html(editContactView.render().el);
}
});
Now in above after listening to event in initialize function, I am calling editContact function and I am also passing model using this keyword. Inside editContact function, I am creating an instance of EditContact, view which is following, and then rendering a form which needs to be shown.
App.Views.EditContact = Backbone.View.extend({
template: template('editContactTemplate'),
render: function () {
var html = this.template(this.model.toJSON()); //<--- this is line 57
this.$el.html(html);
return this;
}
});
After doing all of the above, the form is not shown and I am getting above mentioned error.
Question
How do I pass model to render function inside EditContact via router so that it starts working?
UPDATE
Here is my model
App.Models.Contact = Backbone.Model.extend({
urlRoot : '/contacts'
});
In your editContact method the argument contact refers to the id you pass onwards from the router. When you initialize a new view with new App.Views.EditContact({ model: contact }) the model of the view will, expectedly, be the id.
You need to map the id into a model instance. IMHO the correct place to do this is in the router:
editContact: function (id) {
var contact = new App.Models.Contact({id:id});
vent.trigger('contact:edit', contact);
}
Notice that at this point the model will only have the id property set. If you need to populate the model properties for editing, you should fetch the model from the server, and only then trigger the event:
editContact: function (id) {
var contact = new App.Models.Contact({id:id});
contact.fetch().done(function() {
vent.trigger('contact:edit', contact);
});
}
Edit based on comments: Generally speaking you shouldn't pass anything to the router. The router should be a starting point for every new request (url change). If you want to hold some state between page changes, you should store the data on the router level, and pass the models and collections "down" from the view.
In a simplified scenario this would mean initializing and storing a reference to the collection in the router. Something like:
var Router = Backbone.Router.extend({
initialize: function() {
this.contactCollection = new App.Collections.Contacts();
},
editContact: function (id) {
id = parseInt(id, 10);
if(_.isNaN(id)) {
//error...
}
//try to get a model from the collection
var contact = this.contactCollection.get(id);
//if not found, create, add and fetch
if(!contact) {
contact = new App.Models.Contact({id:id});
this.contactCollection.add(contact);
contact.fetch().done(function() {
vent.trigger('contact:edit', contact);
});
} else {
vent.trigger('contact:edit', contact);
}
}
});
Please note that this is just example code, and not necessarily how you should implement it, line by line. You should consider whether it's OK to display a potentially stale model in the view, or whether you should always fetch it from the server. In practice you might also abstract the collection state in a nice class, instead of handling it directly in the router.
Hope this answers your questions.

Order of events triggerd by Backbone.Router when using navigate

I am using Backbone's "all" event to catch all route events in my app in order to log the page views. This works well as long as I don't use navigate to manually trigger a route.
In the following example, I forward the user from the dashboard route to the login route. Backbone fires the event AFTER the route callback is executed, leading to the following output:
showDashboard
showLogin
route:showLogin
tracking:/login
route:showDashboard
tracking:/login
Obviously this is not what I want. I know I could call showLogin instead of using navigate to trigger the login route and this is what I am doing right now, but I would like to know why the order of the events is not the same than the order of the triggered callbacks.
Here is my router (shortened):
var AppRouter = Backbone.Router.extend({
routes: {
"/login": "showLogin",
"": "showDashboard",
},
initialize: function() {
return this.on('all', this.trackPageview);
},
trackPageview: function(eventName) {
console.log(eventName);
var url = Backbone.history.getFragment();
console.log('tracking: ' + url);
},
showDashboard: function() {
console.log('showDashboard');
// check if the user is logged in etc.
this.navigate('#/login', { trigger: true });
},
showLogin: function() {
console.log('showLogin');
}
});
Backbone's Router is actually very simple, and if you read the code you'll see the following in it's constructor:
this._bindRoutes();
this.initialize.apply(this, arguments);
_bindRoutes attaches all your routes as you expect, and it does this before your initialize function gets called. So your binding will always fire after Backbone's does.
You're probably going to be better off finding another way to do this.
You could call a before type function yourself in your routes to do stuff like track pageviews/etc. Or maybe you could just override route, track your pageview and then make sure to call Backbone's implementation with something like Backbone.Router.prototype.route.call(arguments);

Backbone router creates multiple views which causes multiple events to bind to the same view

I'm new to backbone.js and trying to understand how routes, views etc works and now I have a problem with events building up for the same view. here is a clip that will show you exactly what I mean. http://screencast.com/t/QIGNpeT2OUWu
This is how my backbone router looks like
var Router = Backbone.Router.extend({
routes: {
"pages": "pages",
}
pages: function () {
var page_view = new PageView();
}
});
So when I click the Pages link I create a new PageView and this is the code I'm using
PageView = Backbone.View.extend({
el: $("#content"),
initialize: function () {
$.ajax({
url: '/pages',
success: function (data) {
$("#content").html(data);
}
});
},
events: {
"click td input[type=checkbox]": "updatePublishedStatus"
},
updatePublishedStatus: function (event) {
console.log('update publish status');
}
});
pretty basic I guess but as you can see in the clip each time I navigate to /pages I get another event registered to the checkbox.
There are a few things going wrong here.
Your video indicates pages being a collection well, Pages. Pages being a Backbone.Model with attributes such as Page name, slug, published etc... You lack that and it's going to hurt. You shouldn't just load some html and push it to your DOM, this defies the whole purpose of using Backbone in the first place.
If you do create a Model for a Page it will have a View. Then your /pages route will show the view of the Collection Pages etc.
You will fetch your data not inside a view's initialize but rather by doing pages.fetch(); where pages is an instance of the Pages collection. This can happen before you even initialize your router.
When changing attributes through your view, the individual models will be updated.
As a sidepoint: Fetching data on initialize is not great. You can call render() before you actually get the data and that's no fun.
Also instead of doing $('#content') you can use the view's $el. As in this.$el.html(...);
Move var page_view = new PageView() to be outside of Router.pages().
Have the PageView.initialize() success callback save data to a variable. Either in PageView or in a model.
Add a render function to PageView that sets $("#content").html(data);.
Call page_view.render() within Router.pages().

Categories