10

I currently have a working profile update form using purely craft but I would like to now enhance that using Vue and Axios to update via Ajax. I may well submit a couple of questions rather than put them all in one to try and help others. For now my main two issues are:

  • Craft doesn't seem to like how I am supplying the CSRF so I have set it to false for now to test but I need that working.
  • When I submit the form with with CSRF set to false in my config I get a 200 success response but the user is not updated.

I suspect in both cases I am just misunderstanding how Craft expects to see the data sent, here is my code (simplified to one form field for now).

<form method="post" action="" accept-charset="UTF-8" @submit.prevent="submitForm">
  {{ getCsrfInput() }}
  <input type="hidden" name="action" value="users/saveUser">
  <input type="hidden" name="redirect" value="members/profile/{% include '_includes/helpers/user_profile_link' %}">
  <input type="hidden" name="userId" value="{{ currentUser.id }}">

  <div class="control">
    <label for="first-name" class="label">First Name</label>
    <input id="first-name" class="input" type="text" name="firstName" value="" v-model="user.firstName">
  </div>

  <button type="submit" class="button is-primary">Save Profile</button>
</form>

Here is my Vue.js code, I have a div with an id of app around the whole form and Vue is working as it should with the model binding:

var app = new Vue({
  delimiters: ['@{', '}'],
  el: '#app',
  data: {
    user: {
      firstName: '{{ currentUser.firstName }}'
    }
  },
  methods: {
    submitForm: function () {

      var config = {
        responseType: 'json'
      };

      var data = this.user;

      data[window.csrfTokenName] = window.csrfTokenValue; // Append CSRF Token

      axios.post('/', data, config)
           .then(function(response) {
             console.log('saved', response, data)
           });
    }
  }
});

Here is my response from the console:

Network headers in console

Here is my console log from the submitForm method:

Console log from submitForm method

Finally here is my craft log for the form post:

$_SERVER=array (
  'HTTP_HOST' => 'tfa.dev',
  'HTTP_CONNECTION' => 'keep-alive',
  'CONTENT_LENGTH' => '125',
  'HTTP_ACCEPT' => 'application/json, text/plain, */*',
  'HTTP_ORIGIN' => 'http://tfa.dev',
  'HTTP_USER_AGENT' => 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36',
  'CONTENT_TYPE' => 'application/json;charset=UTF-8',
  'HTTP_REFERER' => 'http://tfa.dev/members/profile/edit/gareth',
  'HTTP_ACCEPT_ENCODING' => 'gzip, deflate',
  'HTTP_ACCEPT_LANGUAGE' => 'en-US,en;q=0.8',
  'HTTP_COOKIE' => '40e5c202a55b621f12b3289f66fe4061username=0298fcd1dea0b36e656a86a889e064baf094d01cs%3A84%3A%2290509830d9f39442177d77334e3cfc3eebb72a07czoyNDoiZ2FyZXRoQGdhcmV0aHJlZGZlcm4uY29tIjs%3D%22%3B; 00b542cb70dd8d427c43ee85c4523092username=0e93371fd3c232c26316e28b35a48feadfae92b5s%3A92%3A%22d736848c422c74ac22e0fba4ab7cec817bfa3da8czoyOToidGVzdG1lbWJlcjNAZ2FyZXRocmVkZmVybi5jb20iOw%3D%3D%22%3B; CraftSessionId=f0e0cea11a268bef98340313df175698; YII_CSRF_TOKEN=c447a0d39e762b38c2e68f6aee96339a633cd654s%3A81%3A%225DGNNDo2iYd6E0FBYBehrXDGn4Ns9uMpQDE3ICPk%7C65cf935623e7404164bc9dce5ae11221666cbe32%22%3B; 00b542cb70dd8d427c43ee85c4523092=77140dec55873e6c5621e2bfb211d3fbe977eae6s%3A428%3A%2214c20ffabe93387c4b6fc93f8c7d497848337bb6YTo2OntpOjA7czoyOToidGVzdG1lbWJlcjNAZ2FyZXRocmVkZmVybi5jb20iO2k6MTtzOjMyOiI1ck50REhyMEltR2RNRDNRYkJpRnR%2BQW4xNzNuTnBmbCI7aToyO3M6MzY6ImUzOGQyNTJiLTgyYzAtNDBiZi1hYTY3LTFjY2FmMjYzN2QxYSI7aTozO2k6MDtpOjQ7czoxMjA6Ik1vemlsbGEvNS4wIChNYWNpbnRvc2g7IEludGVsIE1hYyBPUyBYIDEwXzEyXzMpIEFwcGxlV2ViS2l0LzUzNy4zNiAoS0hUTUwsIGxpa2UgR2Vja28pIENocm9tZS81Ni4wLjI5MjQuODcgU2FmYXJpLzUzNy4zNiI7aTo1O2E6MDp7fX0%3D%22%3B',
  'PATH' => '/usr/bin:/bin:/usr/sbin:/sbin',
  'SERVER_SIGNATURE' => '',
  'SERVER_SOFTWARE' => 'Apache',
  'SERVER_NAME' => 'tfa.dev',
  'SERVER_ADDR' => '::1',
  'SERVER_PORT' => '80',
  'REMOTE_ADDR' => '::1',
  'DOCUMENT_ROOT' => '/Users/garethredfern/Code/tfa/public',
  'SERVER_ADMIN' => '[email protected]',
  'SCRIPT_FILENAME' => '/Users/garethredfern/Code/tfa/public/index.php',
  'REMOTE_PORT' => '49557',
  'GATEWAY_INTERFACE' => 'CGI/1.1',
  'SERVER_PROTOCOL' => 'HTTP/1.1',
  'REQUEST_METHOD' => 'POST',
  'QUERY_STRING' => '',
  'REQUEST_URI' => '/',
  'SCRIPT_NAME' => '/index.php',
  'PHP_SELF' => '/index.php',
  'REQUEST_TIME_FLOAT' => 1487494935.3640000820159912109375,
  'REQUEST_TIME' => 1487494935,
  'argv' => 
  array (
  ),
  'argc' => 0,
)

From what I can see the data is not actually going through to Craft but I am getting a success response.

My Working Code

Selvin jumped on a hangout with me to help out, the easiest solution was to work with Vue resource as Selvin has answered below but I had another couple of issues. Mainly that I wasn't passing the userId or the action which are both hidden fields in my form via ajax. So here is my final JS code which worked with the form above. I have switched to using vue-resource.

Vue.http.options.emulateJSON = true;
Vue.http.options.emulateHTTP = true;

var app = new Vue({
  delimiters: ['@{', '}'],
  el: '#app',
  data: {
    user: {
      firstName: '',
      userId: '{{ currentUser.id }}',
      action: 'users/saveUser'
    }
  },
  methods: {
    submitForm: function () {

      var data = this.user;

      data[window.csrfTokenName] = window.csrfTokenValue; // Append CSRF Token

      this.$http.post('/', data)
        .then(function(response) {
          console.log('saved', response);
        });
    }
  }
});

5 Answers 5

9

Unlike Vue Resourse (if you've used it), Axios does not have a good way to emulateJSON on legacy servers or frameworks that do not understand submissions sent as application/json. Your data gets to Craft but it is not understood, leaving POST empty. You would need to have Axios submit to your own controller, where you can read the raw data, JSON decode it, and update the user profile with that data. Alternatively, you can tell Axios to send the data as application/x-www-form-urlencoded (https://www.npmjs.com/package/axios#using-applicationx-www-form-urlencoded-format) data. However, you'll still need to read the data and decode it yourself because Axios still sends the data as a JSON string rather than form fields.

CSRF token validation fails for the same reason.

As a quick solution, I'd use vue resource with emulateJSON and emulateHTTP set to true. Integrates well with Vue and its API is very similar to Axios. Of course, once you figure out Axios, you can easily swap it out.

If you implement your own controller action to update the user, I believe you should access the data sent by Axios with craft()->request->getRawBody() and then decode it into an array with JsonHelper::decode().

Update: As of Craft 3.3 you can submit JSON requests to any Craft controllers, as long as they contain a Content-Type: application/json header.

2
  • 1
    Thanks Selvin, I knew you would have this one covered :-) I'll give Vue resource a try and report back here. Once I get thinks working I might well dive into Axios and try my own controller but baby steps for me work well.
    – Gareth
    Commented Feb 19, 2017 at 14:28
  • 1
    That's a good plan 👍 Commented Feb 19, 2017 at 14:31
2

I have it working like this:

First set axios defaults to

window.axios.defaults.headers.common = {
    'X-Requested-With': 'XMLHttpRequest'
};

Then in your component I use URLSearchParams() to convert object to encoded url.

For example my user login method might look like this:

methods: {
    login() {
        var params = new URLSearchParams();
        params.append(window.csrfTokenName, window.csrfTokenValue);
        // The rest of the required fields for user login
        // add the axios.post('/', params) call bellow
    }
}

The token stuff is just coming from my _layout.html file

<script type="text/javascript">
    window.csrfTokenName = "{{ craft.config.csrfTokenName|e('js') }}";
    window.csrfTokenValue = "{{ craft.request.csrfToken|e('js') }}";
</script>
1

Perhaps it could be a issue with twig also uses the mustaches {{}}

Have a look at this blog post by Andrew Welch.
(he is setting up different delimiters for Vue)
https://nystudio107.com/blog/using-vuejs-2-0-with-craft-cms

2
  • Thank you @bomanden - I am using different delimiters for my output delimiters: ['@{', '}'], and the twig tags are being parsed in the Vue template no problem as I can see my test in the data in the Vue console. I am pretty sure its down to how I am passing the data to Craft.
    – Gareth
    Commented Feb 19, 2017 at 14:17
  • Sorry , just saw that you are actually doing that already. I'll hold on to the post, want to know the answer as well.
    – bomanden
    Commented Feb 19, 2017 at 14:22
1

I have it working with axios. You have to set this header in the config array: 'X-Requested-With': 'XMLHttpRequest' ;

var config = {
       responseType: 'json',
       headers: {'X-Requested-With': 'XMLHttpRequest', 'Content-Type': 'application/x-www-form-urlencoded'}
    };

If you look at craft's getIsAjaxRequest()

public function getIsAjaxRequest()
{
    return isset($_SERVER['HTTP_X_REQUESTED_WITH']) && $_SERVER['HTTP_X_REQUESTED_WITH']==='XMLHttpRequest';
}

It's looking for the X_REQUESTED_WITH header and for it to be set XMLHttpRequest.

On top of that you have to tell Axios to send the data as application/x-www-form-urlencoded. https://www.npmjs.com/package/axios#using-applicationx-www-form-urlencoded-format

I used the URLSearchParams solution.

1

I got this working after some tinkering. There was no one example I could find to help clear this up so hopefully mine will be useful to others. This is using Vue, Axios, and the Guest Entries plugin, but I had to also use the qs stringifying library to avoid getting a 400 error.

I avoided the Using application/x-www-form-urlencoded format mentioned above because URLSearchParams isn't fully supported yet, as explained in the Axios docs. Instead, those docs suggest using qs as an alternative but I learned that's only when you're bundling it. My setup required me to use Qs (with a capital Q).

new Vue({
    el: '#app',
    delimiters: ['[[', ']]'],
    data() {
        return {
            contactForm: {
                firstName: '',
                lastName: '',
                email: '',
                message: ''
            }
        }
    },
    methods: {
        onContactFormSubmit(event) {

            // prep the form data
            let data = {
                fields: this.contactForm,
                sectionUid: 'xxxxxx-for-guest-entries-plugin'
            };

            // add csrf token
            data[window.csrfTokenName] = window.csrfTokenValue;

            // stringify data. Notice the capital Q; you may need this to be lowercase
            let dataString = Qs.stringify(data);

            // add yer headers
            let config = {
                headers: {
                    'X-Requested-With': 'XMLHttpRequest',
                    'Accept': 'application/json'
                }
            };

            // post it
            axios.post('/actions/guest-entries/save', dataString, config)
            .then(function (response) {
                console.log(response);
            })
            .catch(function (error) {
                console.log(error);
            });
        }
    }
});

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.