0

I think someone must have run into this situation before. Basically I have a big "form" which is composed of multiple smaller "forms" inside. (In fact, they are not real forms, just sets of inputs that are grouped together to collect info for models).

This form is for a checkout page, which contains:

  1. shipping address
  2. shipping method
  3. billing address
  4. billing method
  5. other additional info such as discounts code input, gift wrapping etc.

I would like to update the user filled info to the server as soon as they complete each part (for example, when they complete shipping address). However, I want to make it work seamlessly without the need for the users to click some kind of "update" button after filling each partial part. I wonder if there is some way to go around this?

mr1031011
  • 3,574
  • 5
  • 42
  • 59

2 Answers2

0

You'll want to $watch the fields in question and act upon them (say save to db) when they are filled in. The issue you will run into is how to determine when a user has filled fields in. Things like onblur etc don't work very well in practice. I would recommend using what is called a debounce function which is basically a function that allows the user to pause for X amount of time without our code going "ok done! now let's.. ohh wait still typing..."

Here's an example that I use on my own cart - I want to automatically get shipping quotes once I have an address so I watch these fields, allow some pausing with my debounce function then call my server for quotes.

Here's some controller code:

// Debounce function to wait until user is done typing
function debounce(fn, delay) {
  var timer = null;
  return function() {
    var context = this,
      args = arguments;
    clearTimeout(timer);
    timer = setTimeout(function() {
      fn.apply(context, args);
    }, delay);
  };
}

// Apply debounce to our shipping rate fetch method
var fetch = debounce(function() {
  $scope.fetching = true;
  cartService.updateShipping($scope.shipping, function(data) {
    $scope.fetching = false;
    $scope.quotes = data;
  });
}, 1000);

// Watch the shipping fields - when enough done and user is done typing then get quote
$scope.$watch('shipping', function(newVal, oldVal) {
  // I use this to play around with what fields I actually want before I do something
  var fields = ['street', 'region', 'name', 'postal', 'country', 'city'];
  var valid = true;
  fields.forEach(function(field) {
    if (!$scope.form[field].$valid) {
      valid = false;
    }
  });
  if (valid) fetch();
}, true);

My form fields are setup like this:

<input type="text" name="street ng-model="shipping.street" required>
<input type="text" name="name" ng-model="shipping.name" required>

Notice how I make them part of a "shipping" object - that allows me to watch the shipping fields independently of others such as billing.

Note that the above is for the extreme cases such as shipping fields. For simple things such as subscribing to a newsletter if they check a box then you don't need to use the above and can simply do an ng-click="spamMe();" call in your checkbox. That function (spamMe) would be in your controller and can then call your server etc...

var spamMe = function() {

  // Grab the email field that might be at top - ideally check if it's filled in but you get the idea
  var email = $scope.email;
  $http.post('/api/spam', ....);
}
Community
  • 1
  • 1
cyberwombat
  • 38,105
  • 35
  • 175
  • 251
0

I'd apply a $scope.$watch on each of those variables to trigger a function that checks to see if all the fields for a given section are filled out, and if so, then submit it to the server as an ajax request.

Here's my attempt at writing this:

var shippingFields = ['address', 'city', 'state', 'zip']     // etc

function submitFieldsWhenComplete(section, fields) {
    fieldValues = fields.forEach(function (field) {
        return $scope[section][field]
    });

    if (fieldValues.every()) {
        // We've got all the values, submit to the server

        $http.post({
            url: "/your/ajax/endpoint",
            data: $scope.shipping
        })
    }
}

shippingFields.forEach(function(field) {
    $scope.$watch(function() {
        return $scope['shipping'][field]
    }, function(val) {
        submitFieldsWhenComplete('shipping', shippingFields);
    });
});
Kevin Stone
  • 8,831
  • 41
  • 29