9

I 'm learning Backbone.js for a new app I'm building. I need to perform an AJAX call(REST SERVICE) to authenticate.

Where is the correct place for this call? In the Model, View or somewhere else? specifically related to Backbone.js MVC model.

<html>
<head>
<script src="lib/jquery-1.6.1.min.js"></script>
<script src="lib/json2.js"></script>
<script src="lib/underscore-min.js"></script>
<script src="lib/backbone-min.js"></script>   

<script language="javascript">
      $(function(){
         var LoginView = Backbone.View.extend({
          el: $("#login-form"),

          events: {
            "click #login": "login"
          },

          login: function(){
              alert("Hello");
           }
        });

        window.LoginView = new LoginView();
      });
    </script>   
  </head>
  <body>
    <form action="/login" id="login-form">
      Username: <input type="text" id="username"><br>
      Password: <input type="password" id="password"><br>
      <button id="login">Login</button>
    </form>
  </body>
</html>
John Cooper
  • 7,343
  • 31
  • 80
  • 100

3 Answers3

13

Create an authentication Model, that stores the post state (username, password, remember me) as well as the response state (login failed, login accepted)...

App.Model.authentication= Backbone.Model.extend({
    defaults: {
        Username: "",
        Password: "",
        RememberMe: false,
        LoginFailed: false,
        LoginAccepted: false
    },
    url:"api/authentication"
});

Update the View to use the model:

   $(function () {
    var LoginView = Backbone.View.extend({
        model: new App.Model.authentication(),
        el: $("#login-form"),
        events: {
            "click #login": "login"
        },

        login: function () {
            this.model.save({username: this.$el.find("#username"),
                password: this.$el.find("#password")}, {
                success: function () {
                    /* update the view now */
                },
                error: function () {
                    /* handle the error code here */
                }
            });
        }
    });
}

);

Calling Model.Save() will issue a post request to the server, although in this instance on the server you're not actually saving anything, but check the credentials and sending back an object with the same model, but with the "LoginAccepted" field set appropriately.

Don't implement custom JQuery AJAX Posts - Its not in the spirit of backbone, which handles it all for you under the hood via its REST interface.

More details on the REST interface and the various REST Actions and URLS that backbone uses here: http://codebyexample.info/2012/04/30/backbone-in-baby-steps-part-3/

One more thing on the AJAX vs model.save() debate. If your application was a stateless chat room like IRC - which sends messages to other users in the chat room but doesn't save the messages centrally... would you throw away all of backbone's REST functionality and re-implement them with custom AJAX calls because you're not ACTUALLY 'saving', you're really just 'sending'. That would cost you a huge amount of work to re-implement functionality that's already there, just because of semantics. Always read model.save() as model.post() - its not just for saving - its for sending.

Handsome Nerd
  • 17,114
  • 22
  • 95
  • 173
reach4thelasers
  • 26,181
  • 22
  • 92
  • 123
  • 1
    It's a hack to call Backbone save() on something you do not intend to change. And it's better to have a CurrentUser model that has current user data instead of another model just for auth purpose. – mvbl fst Jul 04 '12 at 16:33
  • 2
    I'd say its a hack to intertwine ajax calls inside backbone when backbone is REST enabled and takes care of such things for you: save is just the name of a method that posts data to the server - it could just as well be named 'post', 'submit' or 'authenticate'. Our code does EXACTLY the same thing. Backbone carries out that exact $POST request for you. – reach4thelasers Jul 04 '12 at 17:46
  • 1
    Here's the deal. You will learn if you have not already that you can NOT do everything you need in Backbone without hacking some concept. IMO using Ajax is less of a hack than using model.save() for auth (what exactly are you saving?) – mvbl fst Jul 04 '12 at 18:40
  • @reach4thelasers: why do we pass an empty object in the save method and what real purpose does collection do when i have models. – John Cooper Jul 04 '12 at 20:36
  • The empty function can contain the parameters of the model you want to update, and the values that you want to update them to. If you've updated the model elsewhere then calling model.save with an empty object posts the state of the object. If the object is non-empty, it updates the specified values in the Model and then POSTs to the server. I've updated the answer to show this. – reach4thelasers Jul 04 '12 at 21:51
  • Collections are, as the name suggests, collections of models. Say you were rending out social networking stories. You'd have a stories collection comprised of story models. You might want to get all stories - you call fetch() on the collection. You might want to get one item from the collection - you call collection.fetch(34). But when updating a single model you call model.Save() since you're only updating one - not the whole collection – reach4thelasers Jul 04 '12 at 21:58
  • Here are some useful links: http://backbonetutorials.com/what-is-a-model/ http://backbonetutorials.com/what-is-a-collection/ And if you REALLY want to get Backbone nailed down in under 2 hours. I'd highly recommend the Peepcode backbone screencasts https://peepcode.com/screencasts (Basics, Interaction and Persistence) Definitely the best backbone training on the web. Well worth the money (I'm not affiliated by the way) – reach4thelasers Jul 04 '12 at 22:02
  • @reach4thelasers: Few final questions: when i would go with collections and how would i use these parameters... RememberMe: false, LoginFailed: false, LoginAccepted: false on success how would i redirect him to other page. – John Cooper Jul 05 '12 at 09:43
  • Well you'd go with collections if you had a view that contained multiple sub-views. For example a BuddyList that has a collection of BuddyViews - the BuddyListView has a collection (not a model), and its render function loops through each model in the collection and creates a new BuddyView for each model and calls render on the buddy view. That way the BuddyListView is responsible for responding to add/remove events on the collection for changing the buddy list. But the individual BuddyView handles specific events to a particular buddy - like 'click' to start a chat with that buddy – reach4thelasers Jul 05 '12 at 10:00
  • Your other question. If login failed you need to write a message on the login box to show this so the user knows what happened and should try again. So you might want to re-render the login box view based on the new model that backbone received from model.save(). If the model has login failed, write a message. Your template would look like this <% if (loginFailed) { %> Username/Password not recognised <% } %> so re-rendering the template would display the error. – reach4thelasers Jul 05 '12 at 10:09
  • 1
    One more thing on the AJAX vs model.save() debate. If your application was a stateless chat room like IRC - which sends messages to other users in the chat room but doesn't save the messages centrally... would you throw away all of backbone's REST functionality and re-implement them with custom AJAX calls because you're not ACTUALLY 'saving', you're really just 'sending'. That would cost you a huge amount of work to re-implement functionality that's already there, just because of semantics. Always read model.save() as model.post() - its not just for saving - its for sending. – reach4thelasers Jul 05 '12 at 10:21
  • I'd suggest not catching `click`, but instead wrapping the form in `
    `, making that `$el` and catching `submit #login-form`. See [this answer for more information](http://stackoverflow.com/a/29465198/124486).
    – Evan Carroll Apr 06 '15 at 17:49
  • @reach4thelasers I aknowledge that your chatroom exemple makes sense and that it's cool to take advantage of backbone's own REST methods. But say you have an auth method, anotherone for lost password, with non-REST url like `/users/lostpassword/`, would you create a model for each one as they don't follow the same url pattern? In that case @mvblfst 's method seems more appropriate to me, or is there a workaround I might have missed? – Buzut Sep 26 '16 at 18:01
  • I've not used backbone for years. But I would say that "lostpassword" should be a different resource to "authenticate". Read up on the first principle of REST - Uniform Interface. – reach4thelasers Sep 28 '16 at 18:22
11

You always start in the view because your DOM interactions (incl. form submits) happen in views.

Then, if I were you, I would add a custom method to the User model, and call the method from the view. The login method does not exactly fit with native Backbone sync as you're not trying to add / update / retrieve anything (mind that you DO NOT want to load user password or password hash from the server for security reasons). So you do a regular Ajax call without calling Backbone fetch(). So, here you go:

var UserModel = Backbone.Model.extend({
  ...
  checkLogin: function(name, password) {
    var self = this;
    $.post('/login', { name: name, password: password }, function(data) {
      self.set(data); // data should be in JSON and contain model of this user
    }, 'json').error(
      self.trigger('loginError'); // our custom event
    );
  }
});

And the view:

var UserView = Backbone.View.extend({
  events: {
    'click .login': 'loginSubmit'
  },

  initialize: function() {
     _.bindAll(this, 'loginSubmit', 'loginSuccess', 'loginError');
     this.model.bind('change', this.loginSuccess);
     this.model.bind('loginError', this.loginError);
  },

  loginSubmit: function() {
    var name = 'username', // you get it from some element
        password = '***'; // you get it from another element
    this.model.checkLogin(name, password);
  },

  loginSuccess: function() {
    alert ('You have been logged in');
    console.log(this.model); // should log loaded user model
  },

  loginError: function() {
    alert ('Problem with login');
  }
});

Make sure you pass the UserModel instance to the UserView instance, e.g.:

var userModel = new UserModel,
    userView = new UserView({ model: userModel });
Evan Carroll
  • 78,363
  • 46
  • 261
  • 468
mvbl fst
  • 5,213
  • 8
  • 42
  • 59
  • Got a question. Why are you triggering loginSuccess when 'change'? – Miguel Jiménez May 20 '13 at 19:33
  • Hey, I can't remember where this code sample is from but it looks like right above, in UserModel, I have `self.set(data)` in post callback. It might be emitting `change` on user model in `set()` method. – mvbl fst May 20 '13 at 20:17
  • I believe it's worth pointing out about this example that the AJAX request (e.g., business logic) is placed in the model and not view, as it should be in a proper MVC implementation (backbone.js documentation also mentions putting business logic in models). – Caleb G Sep 25 '13 at 02:26
0

ReST is NOT a standard, simply a spec and any practical developer will mix ReST and RPC when appropriate to do so. That said, neither of these answers pointed out the fact that the server response should return a token (sessionID, JSON web token, etc.) and delete the plain text password. Failing to do so introduces a security risk.

srquinn
  • 10,134
  • 2
  • 48
  • 54