[Solved] Register Form - How to POST data to backend server


#1

Hello,

I’m looking to create a register form and I’m not sure what would be the best way to go about doing it. From what I’ve gathered I should use AJAX to POST the email, password, and password_confirmation to the backend.

Not sure if I would use the model to post the data to the backend or if I need to pull the email, password, and password_confirmation out of the form and send them to the backend.

If anyone could help me or point me in the right direction I’d appreciate it.

Thanks


#2

Hey @MattsLab, I wouldn’t say there are hard and fast rules but here is a rule of thumb I generally use:

Generally you’d want to use the model to POST data if the data you are sending is used to describe a “resource”. For example if your form was going to create a user on your server, and the form contents were pretty close or the same as the user model on your server, it might make sense to use an Ember Data model. The canonical example of this would be creating a blog post. If you had a form that took a title, post body, author, and maybe a couple other params, and you wanted it to post to your server to create a “blog post” resource, an Ember Data model would be a good idea. The nice thing about using Ember Data to do it is that it takes care of a lot of the boilerplate AJAX stuff for you and all you need to do is define your model, define an adapter and/or serializer (only if you need to munge the data on either end), create a model instance <recordname> in your Ember controller, and then do a <recordname>.save() and the save would trigger a POST.

Not knowing how your server looks and exactly what your POST is doing, I’d say maybe an AJAX call might fit your use case a little better. If you wanted to go the AJAX route instead you could do something like this in your controller:

Ember.$.ajax({
  url: <your-POST-resource-url>,
  type: "POST",
  data: JSON.stringify({
    email: <email>,
    password: <password>,
    etc...
  })
}).then(function(resp){
  // handle your server response here
}).catch(function(error){
  // handle errors here
});

Obviously there are lots of other AJAX config options you could use like headers, content type, etc. The thing to remember is that if you use AJAX you are sidestepping Ember Data and so if you ever use AJAX to create or fetch a resource that you want in your Ember Data store you’ll have to either fetch it with Ember Data or manually load it in the Ember Data store client-side.


#3

And of course if you wanted to use Ember Data to do this you could do something like the following:

actions: {
  submit(user){
    user.save().then(function(resp){
      // handle a server response if you want
    }).catch(function(error){
      // handle an error, maybe by setting an error message like this
      this.set('errorMessage', error)
    })
  }
}

#4

After a bit of troubleshooting I figured it out. But I still think there is a better way to do it.

components/signup-form.js

import Ember from 'ember';
const { service } = Ember.inject;

export default Ember.Component.extend({
  session: service("session"),

  actions: {
    submit() {
      let user = this.get('user')
      this.attrs.triggerSave(user);
    }
  }
});

controller/signup.js

import Ember from 'ember';

export default Ember.Controller.extend({
  session: Ember.inject.service('session'),

  actions: {
    save(user) {
      let newUser = user;
      newUser.save().catch((error) => {
          this.set('errorMessage', error)
        })
        .then(() => {
          this.get('session')
            .authenticate('authenticator:jwt',
              newUser.get('email'), newUser.get('password'))
            .catch((reason) => {
              this.set('errorMessage', reason.error || reason);
            });
            this.transitionToRoute("index")
        })
    }
  }
});

models/user.js

import DS from 'ember-data';

export default DS.Model.extend({
  name: DS.attr('string'),
  email: DS.attr('string'),
  password: DS.attr('string'),
  passwordConfirmation: DS.attr('string'),
  permissions: DS.attr('number')
});

routes/signup.js

import Ember from 'ember';
import UnauthenticatedRouteMixin from 'ember-simple-auth/mixins/unauthenticated-route-mixin';

export default Ember.Route.extend(UnauthenticatedRouteMixin, {
  model() {
    return this.store.createRecord('user')
  }
});

Now going off of what you said, the best idea would be to use a AJAX call since right now it will send name, email, password, password_confirmation, and permissions to the create method in the backend which accepts JSON like this

{"user":{"name":"Name","email@email.com","password":"password","password_confirmation":"password"}}

So a question I have is how would I get the information from the email, password, password_confirmation into the AJAX call without using the model since that sends information that the server doesn’t need and appears to store raw passwords to ember-data.


#5

You’ve got a couple options. You could either:

Option 1

Not use Ember Data at all (aka delete your models/user.js). Then in the routes/signup.js model hook you’d use something like:

  model() {
    return Ember.Object.create();
  }

and then replace the save action call to newUser.save with an AJAX call like this:

Ember.$.ajax({
  url: <your-POST-resource-url>,
  type: "POST",
  data: JSON.stringify({
    "user": {
      "email": user.get('email'),
      "password": user.get('password'),
      "password_confirmation": user.get('confirmPassword'),
    }
  })
}).then(function(resp){
  // handle your server response here
}).catch(function(error){
  // handle errors here
});

Option 2 (what I would prefer if you aren’t going to use Ember Data for this):

Don’t even use the route model hook at all. I don’t know what your template looks like, but instead of, say, {{input value=content.email}} or whatever you currently have for the email input, just do something like {{input value=emailInput}}. This will bind the value of the input helper to the ‘emailInput’ property on the controller. Then follow suit with password and passwordConfirmation. Then in the controller save action:

Ember.$.ajax({
  url: <your-POST-resource-url>,
  type: "POST",
  data: JSON.stringify({
    "user": {
      "email": this.get('emailInput'), // you're getting this property from the controller now
      "password": this.get('passwordInput'),
      "password_confirmation": this.get('confirmPasswordInput'),
    }
  })
}).then(function(resp){
  // handle your server response here
}).catch(function(error){
  // handle errors here
});

NOTE: even if you want a “user” model on the front end I would definitely remove the password and passwordConfirmation fields from it. You definitely don’t want to store that in the client. If you do waht a user model I would do this form via AJAX as described above, and then do a fetch from the server for the user model later, and set your Ember Data user model to match your whatever your server would return if you, for example did a GET /users/<id>

EDIT: had to make a couple formatting changes, came out weird the first time


#6

From a higher level, your {{input}} helpers basically determine where the values are stored, and therefore where to get them when you want to do the AJAX request.

So if you have, in your template, an input helper that looks like this:

{{input value=content.email}}

What that is telling Ember is that you want to bind the value of your input helper to the property “email” on your “content” object. The “content” object is an Ember-created object that sits on your controller and contains whatever is returned from the route’s model hook. Since, in your current code, you’re returning an Ember Data record from the route model hook, what your code is doing is binding the value of the email input directly to the “email” property on the ED record (that you created in the route model hook). That means to get the email value in your controller you could either call this.get('content.email') from anywhere in the controller, or you could do what you’re currently doing and pass the content object to the action as user and then do user.get('email').

In what I called “Option 1” above, all I’m doing is replacing the creation of an Ember Data record in the model hook with the creation of an empty Ember Object. That will function essentially the exact same way but will keep all of that data out of the ED store.

In what I called “Option 2” above, instead of creating an object in the model hook and then passing it (automagically) into the controller as “content” and then binding your input values to it, I suggested just binding your input values to properties on your controller. So you could make an empty property in your controller like:

...
  emailInput: '',
...

And then in your template, bind the value like so: {{input value=emailInput}}. Then if you want to get the value of the “emailInput” prop anywhere in your controller you’d simply say this.get('emailInput') because this refers to the controller, and you want to get the ‘emailInput’ property to get the current value of the email {{input}} helper.

Hope all that makes sense.


#7

Alright, that is making sense, though a few more questions. For option 2, would I be putting the AJAX call into the save function like this

actions: {
    save() {
      Ember.$.ajax({
        url: ENV.host + "/users",
        type: "POST",
        data: JSON.stringify({
          "user": {
            "name": this.get('nameInput'),
            "email": this.get('emailInput'), // you're getting this property from the controller now
            "password": this.get('passwordInput'),
            "password_confirmation": this.get('passwordConfirmationInput'),
          }
        })
      }).then(() => {
        // Transition
      }).catch(function(error) {
        this.set('errorMessage', error.error || error);
      });
    }
  }

Or would it be done a different way? Currently getting an error jQuery.Deferred exception: this.set is not a function save/

My templates are

templates/components/signup-form.hbs

<form {{action "submit" on="submit"}}>
  <label for="name">Name </label>
   {{input value=nameInput placeholder="enter name"}}
    <label for="email">Email</label>
    {{input value=emailInput placeholder="enter email"}}
  <label for="password">Password  </label> {{input value=passwordInput type="password" placeholder="enter password"}}
  <label for="passwordConfirmation">Password Confirmation</label>
   {{input value=passwordConfirmationInput type="password" placeholder="enter password again"}}
  <button type="submit">Login</button>
</form>
{{#if errorMessage}}
  {{errorMessage}}
{{/if}}

templates/signup.hbs

{{signup-form triggerSave=(action "save") errorMessage=errorMessage}}
{{outlet}}

#8

That should be about right, I think the error is because the this context is different in your success/error handlers. So in code like this:

    save() {
      Ember.$.ajax({
        url: ENV.host + "/users",
        type: "POST",
        data: {...},
      }).then(() => {
        // success handler, 'this' context has changed!
      }).catch(function(error) {
        // error handler, 'this' no longer points to the controller because the funciton context has changed! And since it has changed it doesn't have a '.set' method like our controller did
      });
    }

In your success and error handlers you can’t refer to this the same way that you could in the root of the action, because in the save action, the context means that the ‘this’ keyword points to the controller, but when you make an ajax request it returns a promise, and then the promise triggers the success or error functions that you pass in, and those have a completely different context. Function context is one of the confusing parts of javascript, especially when it comes to async stuff like this.

So the solution (I’m sure there are others) is to do something like this:

actions: {
    save() {
      let self = this; // create a new variable called 'self' that points to the current 'this', which is the controller
      Ember.$.ajax({
        url: ENV.host + "/users",
        type: "POST",
        data: JSON.stringify({
          "user": {
            "name": this.get('nameInput'),
            "email": this.get('emailInput'), // you're getting this property from the controller now
            "password": this.get('passwordInput'),
            "password_confirmation": this.get('passwordConfirmationInput'),
          }
        })
      }).then(() => {
        // Transition
      }).catch(function(error) {
        // self points at the OLD definition of 'this', aka the controller, which is what we want
        self.set('errorMessage', error.error || error);
      });
    }
  }

For more reading on this I’d recommend reading up on javascript function context and binding and such. It’s a pretty big and confusing topic and can take some time to understand. But in this context, basically anytime you have a promise and are doing something like ‘then’ or ‘catch’ and passing in a function, that function that you pass in will have a different context and you’ll have to do something like the “self” trick above if you want that function to have access to the original context.

EDIT: in the solution, note that you could use either ‘this’ or ‘self’ to get the props from the controller. Since you are constructing the ‘data’ property of the ajax call object in the “root” of the save action, you are still in the correct context. For consistency you could change all of the this.get(...) calls to self.get(...)


#9

So I would do something like this?

import Ember from 'ember';
import ENV from '../config/environment'

export default Ember.Controller.extend({
  actions: {
    save() {
      let self = this;
      Ember.$.ajax({
        url: ENV.host + "/users",
        type: "POST",
        data: JSON.stringify({
          "user": {
            "name": self.get('nameInput'),
            "email": self.get('emailInput'), // you're getting this property from the controller now
            "password": self.get('passwordInput'),
            "password_confirmation": self.get('passwordConfirmationInput'),
          }
        })
      }).then(() => {
        // Transition
      }).catch(function(error) {
        self.set('errorMessage', error.error || error);
      });
    }
  }
});

Which at the moment sends a POST to the server with this JSON {"user":{}}


#10

Ah sorry that’s because your form is in a component, and I was thinking it was in the main template. So in that case your {{input}} tags are binding to properties on the component. So in the component submit/save action, you’d want to return an object like this:

return {
  email: this.get('emailInput'),
  password: this.get('passwordInput'),
  //.. etc
}

and then the component should pass that action to the controller, and in the controllers save action you should take that ‘user’ argument like you did before (or you could call it form or something) and then in the ajax call you would say user.get('email') like before. Sorry again for the confusion, I didn’t realize the inputs where in the component. So in summary, the inputs will be bound to props on the component level, the component action handler should get those values and assemble them into an object, and then pass an action (with the object) out to the controller. Then the controller receives that object in its action handler, gets each prop from it in the ajax call, and you should be golden.


#11

That did the trick. Thank you very much for helping me out, you went above and beyond.


#12

Glad I could help! There’s definitely a lot to wrap your head around there.