Rails 4 Angularjs Paperclip jak wgrać plik
Jestem początkujący w manipulowaniu angularjs za pomocą Rails 4, które dostarczają tylko api. staram się stworzyć prostą usługę AngularJS do przesłania pliku. Ale używam Paperclip do zarządzania plikami i mam pewne problemy.
Po pierwsze, nie rozumiem, jak prawidłowo zebrać plik wejściowy. Widziałem wiele pluginów lub dyrektywy fat, aby to zrobić. Ale chcę tylko prostą dyrektywę, która zbierze moje akta i umieści w moim modelu ng.
I wreszcie chcę wiedzieć, czy jest to bardziej efektywne zakodować mój plik w Base64 ?
Mój Kontroler Rails
class Api::EmployeesController < Api::BaseController
def create
employee = Employee.create(employee_params)
if employee.save
render json: employee
else
render :json => { :errors => employee.errors.full_messages }, :status => 406
end
end
def employee_params
params.require(:employee).permit(:first_name,:mobile_phone,:file)
end
end
Mój Serwis Angularjs
angular.module('test').factory 'Employee', ($resource, $http) ->
class Employee
constructor: (errorHandler) ->
@service = $resource('/api/employees/:id',
{id: '@id'},
{update: {method: 'PATCH'}})
@errorHandler = errorHandler
create: (attrs, $scope) ->
new @service(employee: attrs).$save ((employee) ->
$scope.employees.push(employee)
$scope.success = true
$timeout (->
$scope.success = false
), 3000
), @errorHandler
Mój Kontroler Angularjs
angular.module('test').controller "EmployeesController", ($scope, $timeout, $routeParams, $location, Employee) ->
$scope.init = ->
@employeeService = new Employee(serverErrorHandler)
$scope.employees = @employeeService.all($scope)
$scope.createEmployee = (employee) ->
if $scope.employeeFirstName
@employeeService.create (
first_name: $scope.employeeFirstName
last_name: $scope.employeeLastName
promotion: $scope.employeePromotion
mobile_phone: $scope.employeeMobilePhone
nationality: $scope.employeeNationality
social_number: $scope.employeeSocialNumber
born_place: $scope.employeeBornPlace
employee_convention: $scope.employeeConvention
employee_type: $scope.employeeType
), $scope
else
$scope.error = "fields missing"
2 answers
Po kilku dniach rozwiązywania problemów i zastanawiania się, jak działają obie technologie (jestem nowy w obu -.- ), Udało mi się coś zrobić. Nie wiem, czy to najlepszy sposób, ale to działa. Jeśli ktoś ma jakieś ulepszenia, chętnie je usłyszę.
Ogólnie zrobiłem co następuje:
- Tworzenie dyrektywy w AngularJS do obsługi przesyłania plików
- zakodował plik jako łańcuch base64 i podłączył go do obiektu JSON.
- Rails kontroler zdekodował Łańcuch base64 za pomocą StringIO i ponownie podłączył plik do parametrów
- następnie zaktualizowałem lub stworzyłem model z nowymi zaktualizowanymi parametrami.
To było naprawdę okrężne, więc jeśli jest inny sposób, aby to zrobić, chciałbym wiedzieć!
Używam Rails 4 i najnowszej stabilnej wersji AngularJS, Paperclip i Restangular.
Oto powiązany kod:
Angularjs Dyrektywa
var baseUrl = 'http localhost:port'; // fill in as needed
angular.module('uploadFile', ['Restangular']) // using restangular is optional
.directive('uploadImage', function () {
return {
restrict: 'A',
link: function (scope, elem, attrs) {
var reader = new FileReader();
reader.onload = function (e) {
// retrieves the image data from the reader.readAsBinaryString method and stores as data
// calls the uploadImage method, which does a post or put request to server
scope.user.imageData = btoa(e.target.result);
scope.uploadImage(scope.user.imagePath);
// updates scope
scope.$apply();
};
// listens on change event
elem.on('change', function() {
console.log('entered change function');
var file = elem[0].files[0];
// gathers file data (filename and type) to send in json
scope.user.imageContent = file.type;
scope.user.imagePath = file.name;
// updates scope; not sure if this is needed here, I can not remember with the testing I did...and I do not quite understand the apply method that well, as I have read limited documentation on it.
scope.$apply();
// converts file to binary string
reader.readAsBinaryString(file);
});
},
// not sure where the restangular dependency is needed. This is in my code from troubleshooting scope issues before, it may not be needed in all locations. will have to reevaluate when I have time to clean up code.
// Restangular is a nice module for handling REST transactions in angular. It is certainly optional, but it was used in my project.
controller: ['$scope', 'Restangular', function($scope, Restangular){
$scope.uploadImage = function (path) {
// if updating user
if ($scope.user.id) {
// do put request
$scope.user.put().then( function (result) {
// create image link (rails returns the url location of the file; depending on your application config, you may not need baseurl)
$scope.userImageLink = baseUrl + result.image_url;
}, function (error) {
console.log('errors', JSON.stringify(errors));
});
} else {
// if user does not exist, create user with image
Restangular.all('users')
.post({user: $scope.user})
.then(function (response) {
console.log('Success!!!');
}, function(error) {
console.log('errors', JSON.stringify(errors));
});
}
};
}]
};
});
Plik kątowy z dyrektywą
<input type="file" id="fileUpload" ng-show="false" upload-image />
<img ng-src="{{userImageLink}}" ng-click="openFileWindow()" ng-class="{ hidden: !userImageLink}" >
<div class="drop-box" ng-click="openFileWindow()" ng-class=" {hidden: userImageLink}">
Click to add an image.
</div>
Tworzy to ukryte wejście pliku. userImageLink
jest ustawiany w kontrolerze, podobnie jak metoda openFileWindow()
. Jeśli obraz użytkownika istnieje, wyświetla się, w przeciwnym razie wyświetla pusty div z informacją o kliknięciu, aby przesłać obraz.
W kontrolerze odpowiedzialnym za powyższy kod html mam następującą metodę:
// triggers click event for input file, causing the file selection window to open
$scope.openFileWindow = function () {
angular.element( document.querySelector( '#fileUpload' ) ).trigger('click');
console.log('triggering click');
};
Strona Rails
W kontrolerze modelu użytkownika, Mam następujące metody:
# set user params
before_action :user_params, only: [:show, :create, :update, :destroy]
def create
# if there is an image, process image before save
if params[:imageData]
decode_image
end
@user = User.new(@up)
if @user.save
render json: @user
else
render json: @user.errors, status: :unprocessable_entity
Rails.logger.info @user.errors
end
end
def update
# if there is an image, process image before save
if params[:imageData]
decode_image
end
if @user.update(@up)
render json: @user
else
render json: @user.errors, status: :unprocessable_entity
end
end
private
def user_params
@up = params.permit(:userIcon, :whateverElseIsPermittedForYourModel)
end
def decode_image
# decode base64 string
Rails.logger.info 'decoding now'
decoded_data = Base64.decode64(params[:imageData]) # json parameter set in directive scope
# create 'file' understandable by Paperclip
data = StringIO.new(decoded_data)
data.class_eval do
attr_accessor :content_type, :original_filename
end
# set file properties
data.content_type = params[:imageContent] # json parameter set in directive scope
data.original_filename = params[:imagePath] # json parameter set in directive scope
# update hash, I had to set @up to persist the hash so I can pass it for saving
# since set_params returns a new hash everytime it is called (and must be used to explicitly list which params are allowed otherwise it throws an exception)
@up[:userIcon] = data # user Icon is the model attribute that i defined as an attachment using paperclip generator
end
Użytkownik.plik rb miałby taki:
### image validation functions
has_attached_file :userIcon, styles: {thumb: "100x100#"}
#validates :userIcon, :attachment_presence => true
validates_attachment :userIcon, :content_type => { :content_type => ["image/jpg", "image/gif", "image/png"] }
validates_attachment_file_name :userIcon, :matches => [/png\Z/, /jpe?g\Z/]
Myślę, że to wszystko, co jest istotne. Mam nadzieję, że to pomoże. Prawdopodobnie opublikuję to gdzieś indziej trochę wyraźniej, gdy będę miał czas.Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-27 14:52:56
Ale chcę tylko prostą dyrektywę, która zbierze mój plik i włoży mój ng-model
Ng-file-upload po prostu to robi i jest lekkim, łatwym w użyciu rozwiązaniem między przeglądarkami, które obsługuje postęp/przerywanie, przeciąganie i upuszczanie oraz podgląd.
<div ng-controller="MyCtrl">
<input type="file" ngf-select ng-model="files" multiple>
</div>
$scope.$watch('files', function(files) {
for (var i = 0; i < $files.length; i++) {
var file = $files[i];
$scope.upload = $upload.upload({
url: 'server/upload/url',
file: file,
}).progress(function(evt) {
console.log('percent: ' + parseInt(100.0 * evt.loaded / evt.total));
}).success(function(data, status, headers, config) {
console.log(data);
});
}
});
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2016-01-27 21:33:30