signalpoint / angular-drupal

An Angular JS module for Drupal.
GNU General Public License v2.0
116 stars 33 forks source link

Add function for 'attached files' targeted action on node resource #19

Open rhclayto opened 8 years ago

rhclayto commented 8 years ago

Hi,

I needed to use the 'attach files to node resource' targeted action provided by services but didn't find that capability in this module. I got something working & thought I'd share it.

Here's the code for the Angular Drupal module:

// Attach file(s) to node
  this.file_attach = function(nid, data) {
    var options = {
      method: 'POST',
      url: this.restPath + '/node/' + nid + '/attach_file',
      transformRequest: angular.identity,
      headers: {'Content-Type': undefined, 'Accept': 'application/json'},
      data: data,
    };
    return this.token().then(function(token) {
        options.headers['X-CSRF-Token'] = token;
        return $http(options).then(function(result) {
            if (result.status == 200) { return result.data; }
        });
    });
  };

Setting Content-Type: undefined, & transformRequest: angular.identity are essential to making the browser send the data with the proper Content-Type: multipart/form-data & correct boundaries.

Since this targeted action (for some obscure reason) requires multipart/form-data Content-Type POSTs rather than a more REST API/Angular friendly JSON, it requires some fiddling. In my Angular controller I created a FormData() object that gets passed in above as the parameter 'data'. To wit:

$scope.sendFile = function(nid) {
        var data = {
            'files[anything1]': $scope.loadedImage,
            'field_values[anything1][alt]': $scope.imageAlt,
            'field_values[anything1][title]': $scope.imageAlt,
            field_name: 'field_i4l_page_images',
        };

        var fd = new FormData();
        angular.forEach(data, function(value, key) {
            fd.append(key, value);
        });

        drupal.file_attach(nid, fd)
        .catch(function(response) {
            toaster.pop('error', 'Could not save the file.', response.statusText);
                        // Or some other error handling.
        })
        .then(function(data) {
            console.log(data);
                        // Or some other cool action
        });
    }

According to comments in Services code: 'The name="files[anything]" format is required to use file_save_upload().'

In my example above, $scope.loadedImage is the ng-model set on my file element. According to this article, ng-model doesn’t work on inputs with type=“file”, so I used the custom directive described there to make it work:

.directive('fileModel', ['$parse', function ($parse) {
    return {
        restrict: 'A',
        link: function(scope, element, attrs) {
            var model = $parse(attrs.fileModel);
            var modelSetter = model.assign;

            element.bind('change', function(){
                scope.$apply(function(){
                    modelSetter(scope, element[0].files[0]);
                });
            });
        }
    };
}]);

&

<input type="file" file-model="loadedImage"/>

Hope this helps, & feel free to include it in Angular Drupal! :camel:

signalpoint commented 8 years ago

@rhclayto This looks great, thank you. I'd recommend creating a Pull Request for this, and then your code can be merged in and yourself given credit for the contributions.

rhclayto commented 8 years ago

I'm new to Github so I'll spend a little time learning about pull requests then do it! Thanks.

rhclayto commented 8 years ago

Okay, I made a pull request. I hope everything's copacetic. Github's tools seems pretty cool.