Converting working Ajax call to Angular Observable - ajax

I have an existing JavaScript application that submits documents (.pdf, .txt...) to Solr for text extraction. I am now trying to convert that capability to an Angular-6 implementation and am struggling with the whole observable pattern. Below is the working js code, followed by my angular component and service .ts files. I think I'm close, but no cigar.
let myReader = new FileReader();
myReader.onloadend = function() {
fileAsBlob = myReader.result;
sendToSolr(fileAsBlob);
};
fileAsBlob = myReader.readAsArrayBuffer(file);
/* Get the unique Id for the doc and append to the extract url*/
let docId = $("#post_docId").val();
let extractUrl = "http://localhost:8983/solr/" + core + "/update/extract/?commit=true&literal.id=" + docId;
/* Ajax call to Solr/Tika to extract text from pdf and index it */
function sendToSolr(fileAsBlob) {
$.ajax({
url: extractUrl,
type: 'POST',
data: fileAsBlob,
cache: false,
jsonp: 'json.wrf',
processData: false,
contentType: false,
echoParams: "all",
success: function(data, status) {
//console.log("Ajax.post successful, status: " + data.responseHeader.status + "\t status text: " + status);
//console.log("debug");
getDocument(docId);
},
error: function(data, status) {
//console.log("Ajax.post error, status: " + data.status + "\t status text:" + data.statusText);
},
done: function(data, status) {
//console.log("Ajax.post Done");
},
});
}
All the above does is use a fileReader to read a local file into an ArrayBuffer, and submits that ArrayBuffer to Solr via an Ajax call. In my success I do call another function (getDocument) which just queries Solr (By docId) for the document I just submitted and displays it. Not beautiful, but it works.
For the angular version I have the following service:
constructor(private http: HttpClient) { }
postDocToSolr(fileAsBlob: any): Observable<any> {
let httpHeaders = new HttpHeaders()
.set('type' , 'POST')
.set('jsonp', 'json.wrf')
.set('processData', 'false')
.set('echoParams', 'all')
.set('Content-Type', 'application/x-www-form-urlencoded')
.set('charset', 'utf-8')
.set('data', fileAsBlob);
let options = {
headers: httpHeaders
};
return this.http.post(this.extractUrl, fileAsBlob, options);
}
}
I tried posting the entire service, but it threw the formatting off so here is the POST part of the service.
And in my component I call the service:
extractText(fileContents: any) {
console.log("In the Document.extractText() method");
//this.processDocument(fileContents);
this.textExtractor.postDocToSolr(fileContents)
.subscribe(data => {
console.log("Debug");
console.log("Data: ") + data;
},
error => {
console.log("Error" + error);
}
);
console.log("Debug");
}
Note I've done the fileReader already and am submitting basically the same ArrayBuffer.
The only hint is the in the error => log the error callback(Right term?)on the observable. I get an error code 400, bad request with the message:
"msg":"URLDecoder: Invalid digit (P) in escape (%) pattern"
Which doen't help me much. I'm wondering if it's an encoding issue (UTF-8) but not sure where to begin. Would appreciate a nudge in the right direction.

It looks like the problem is how angular is encoding your URI, I would open your network tool of choice (network tab, fiddler, etc) and look at the sent request URI of each. I suspect they'll be different.

Well, it's often the small things that trip me up. I needed to set the Content-Type to "false" and everything works fine. I also re-did the creation of the httpHeaders, but I think either way would have worked. The working postToSolr Service method is:
export class TextExtractorServiceService {
extractUrl: string = "http://localhost:8983/solr/tater/update/extract/?commit=true&literal.id=778";
constructor(private http: HttpClient) { }
postDocToSolr(fileAsBlob: any): Observable<any> {
const httpOptions = {
headers: new HttpHeaders({
"jsonp": "json.wrf",
"processData": "false",
"echoParams" : "all",
"Content-Type": "false",
"cache": "false"
})
};
return this.http.post(this.extractUrl, fileAsBlob, httpOptions);
}
}
Thanks to all who took a look.

Related

Custom success message on FilePond file upload

I’m using FilePond 4.30.4 with React and react-filepond 7.1.2. Everything is working great and I can upload files.
My server responds with a file ID, and this comes back down to FilePond. I can see it in the onprocessfile event.
I’d like to include this is the ‘Upload Complete’ message. e.g. ‘Complete, file 12345’. How can I set this?
I’ve tried to update .labelFileProcessingComplete in onprocessfile, but it has no effect. I can see my events being fired and the correct data in the console. Perhaps there is another way to update the 'Upload Complete' label with a custom message for the file.
<FilePond
ref={filePondRef}
oninit={() => handleInit()}
files={files}
onupdatefiles={setFiles}
onprocessfile={ (error, file) => {
if (error) {
console.log('OnProcessFile: we have an error:' + error);
console.dir(error);
return;
}
console.log('OnProcessFile:File processed', file);
console.log('OnProcessFile:set processed message to ', file.serverId);
//This has no effect
filePondRef.current.labelFileProcessingComplete='Completed:-#' + file.serverId;
}
}
labelIdle='Drag & Drop your files or <span class="filepond--label-action">Browse</span>'
server={ {
timeout: 7000,
process: {
url: apiUrl,
method: 'POST',
withCredentials: false,
timeout: 7000,
onload: (res) => {
console.log('onload:and label with res=' + res);
// this has no effect either
filePondRef.current.labelFileProcessingComplete='Completed:' + res;
return res;
}
}
}
}
labelFileProcessingError= {() => {
// replaces the error on the FilePond error label
console.log('labelFileProcessingError: serverResponse is:' + serverResponse );
return serverResponse;
}}
/>

How to POST correctly a form that have data and files with VueJS, Axios and Laravel?

I am posting here as a beginner of VueJS and Laravel. I am stuck with a problem that I can't fix by myself after hours of search.
I would like to know how correctly send and get back the inputs of a form (complex data and files).
Here is the submit method of the form:
onSubmit: function () {
var formData = new FormData();
formData.append("data", this.model.data);
formData.append("partData", this.model.partData);
if (this.model.symbolFile != null) {
formData.append("symbolFile", this.model.symbolFile);
}
if (this.model.footprintFile != null) {
formData.append("footprintFile", this.model.footprintFile);
}
axios
.post("/api/updatecomponent", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
// do something with res
// console.log(res);
})
.catch((err) => {
/* catch error*/
console.log(err);
});
},
The variable Data and PartData contains multiple string fields which will be stored in different tables in my database. Example :
Data
{
string Value,
string Tolerance,
string Power
}
Here is the method of the Controller in the server side:
public function updateComponent(Request $req)
{
$data = $req->input('data');
$partData = $req->input('partData');
$symbolFile = $req->file('symbolFile'); // is null if the user did not modify the symbol
$footprintFile = $req->file('symbolFile'); // is null if the user did not modify the footprint
// etc...
}
I am able to get the files, everything work for that and I can store and read them :)
But, the problem is that I am unable to get back properly my Data or PartDat.
When I do :
dd($partData);
I got as result in the console:
"[object Object]"
I am almost sure that I don't use correctly the FormData but after hours of search, I can't find the good way I should gave the Data and PartData to the FormData.
My code was working well for Data and PartData until I add FormData to support the file upload :(
Thank you for your help :)
Here my working code:
Client side:
var formData = new FormData(); // create FormData
formData.append("subcat", this.subcategory);// append simple type data
formData.append("data", JSON.stringify(this.model.data));// append complex type data
axios // send FormData
.post(url, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
// do something with res
// console.log(res);
})
.catch((err) => {
/* catch error*/
console.log(err);
});
Server side:
public function createComponent(Request $req)
{
$subcategory = $req->input('subcat'); // get the input parameter named 'subcat' (selected subcategory)
$data = json_decode($req->input('data'), true); // get the input, decode the jason format, force to return the result as an array
}
I hope it will help other peoples :)
Simple solution
let data = new FormData();
data.append('image',file_name.value);
_.each(form_data, (value, key) => {
data.append(key, value)
})
console.log('form data',data);
Now you can get data in laravel controller like:
$request->title
$request->description
$request->file

ember.js `afterModel` event handler changes not reflected in Template

I have a route in which I use Ajax (not Ember Data) to pull a record, a costcentre, off the server. The record is intended to populate a template for subsequent edits.
Before the fetch, in beforeModel, an empty costcentre is created using createRecord. After the model processing is complete, in afterModel, the returned data is used to populate the costcentre object in the Data Store.
The fetch of the data is successful and in the debugger the update of the locally stored DS object can be seen to have worked but the changes are not seen in the template.
How can I get the template to populate with the data returned from the server ?
In the route I have this :
beforeModel: function(transition) {
this.set('ccToEdit', this.store.createRecord('costcentre'));
},
model(params) {
return getCCByCCIdent( this.urlbase,
this.currentMOP.currentMOP,
ENV.APP.MATClientCode,
params.cceIdentifier_to_edit);
},
afterModel(ccs, transition) {
//I'm testing this with an API end point that returns a
//list but there will only ever be one item in the list
this.ccToEdit.setProperties(ccs[0]);
},
The getCCByCCIdent looks like this :
export const getCCByCCIdent = function(urlbase, currentMOP, clientCode, targetCostCentreIdent) {
return new Promise(function (resolve, reject) {
if (targetCostCentreIdent.length == 0)
{
resolve([])
}
else
{
var theUrl = `${urlbase}/costcentres/${currentMOP}/${clientCode}/${targetCostCentreIdent}`;
$.ajax({
type: 'GET',
url: theUrl,
success: function (response) {
resolve(response);
},
error: function (request, textStatus, error) {
reject(error);
}
});
}
})
}
The simplest way to do this would be to do a then() on the promise being returned from your Ajax call, set appropriate values after that and then return your model:
model(params) {
return getCCByCCIdent(
this.urlbase,
this.currentMOP.currentMOP,
ENV.
params.cceIdentifier_to_edit
).then(ccs => {
let costCentre = this.store.createRecord('costcentre');
costCentre.setProperties(ccs[0]);
return costCentre;
});
},

Saving blob image in laravel's controller

In my Laravel 5/vuejs 2.6 I upload an image with the vue-upload-component and am sending a requested image blob
I try to save it with the controller code like :
if ( !empty($requestData['avatar_filename']) and !empty($requestData['avatar_blob']) ) {
$dest_image = 'public/' . Customer::getUserAvatarPath($newCustomer->id, $requestData['avatar_filename']);
$requestData['avatar_blob']= str_replace('blob:','',$requestData['avatar_blob']);
Storage::disk('local')->put($dest_image, file_get_contents($requestData['avatar_blob']));
ImageOptimizer::optimize( storage_path().'/app/'.$dest_image, null );
} // if ( !empty($page_content_image) ) {
As result, I have an image uploaded, but it is not readable.
The source file has 5 Kib, the resulting file has 5.8 Kib and in the browser's console I see the blobs path as
avatar_blob: "blob:http://local-hostels2.com/91a18493-36a7-4023-8ced-f5ea4a3c58af"
Have do I convert my blob to save it correctly?
MODIFIED :
a bit more detailed :
In vue file I send request using axios :
let customerRegisterArray =
{
username: this.previewCustomerRegister.username,
email: this.previewCustomerRegister.email,
first_name: this.previewCustomerRegister.first_name,
last_name: this.previewCustomerRegister.last_name,
account_type: this.previewCustomerRegister.account_type,
phone: this.previewCustomerRegister.phone,
website: this.previewCustomerRegister.website,
notes: this.previewCustomerRegister.notes,
avatar_filename: this.previewCustomerRegister.avatarFile.name,
avatar_blob: this.previewCustomerRegister.avatarFile.blob,
};
console.log("customerRegisterArray::")
console.log(customerRegisterArray)
axios({
method: ('post'),
url: window.API_VERSION_LINK + '/customer_register_store',
data: customerRegisterArray,
}).then((response) => {
this.showPopupMessage("Customer Register", 'Customer added successfully ! Check entered email for activation link !', 'success');
alert( "SAVED!!::"+var_dump() )
}).catch((error) => {
});
and this.previewCustomerRegister.avatarFile.blob has value: "blob:http://local-hostels2.com/91a18493-36a7-4023-8ced-f5ea4a3c58af"
where http://local-hostels2.com is my hosting...
I set this value to preview image defined as :
<img
class="img-preview-wrapper"
:src="previewCustomerRegister.avatarFile.blob"
alt="Your avatar"
v-show="previewCustomerRegister.avatarFile.blob"
width="256"
height="auto"
id="preview_avatar_file"
>
and when previewCustomerRegister.avatarFile.blob is assigned with uploaded file I see it in preview image.
I show control with saving function in first topic but when I tried to opened my generated file with kate, I found that it
has content of my container file resources/views/index.blade.php...
What I did wrong and which is the valid way ?
MODIFIED BLOCK #2 :
I added 'Content-Type' in request
axios({
method: ('post'),
url: window.API_VERSION_LINK + '/customer_register_store',
data: customerRegisterArray,
headers: {
'Content-Type': 'multipart/form-data'
}
but with it I got validation errors in my control, as I define control action with request:
public function store(CustomerRegisterRequest $request)
{
and in app/Http/Requests/CustomerRegisterRequest.php :
<?php
namespace App\Http\Requests;
use App\Http\Traits\funcsTrait;
use Illuminate\Foundation\Http\FormRequest;
use App\Customer;
class CustomerRegisterRequest extends FormRequest
{
use funcsTrait;
public function authorize()
{
return true;
}
public function rules()
{
$request= Request();
$requestData= $request->all();
$this->debToFile(print_r( $requestData,true),' getCustomerValidationRulesArray $requestData::');
/* My debugging method to write data to text file
and with Content-Type defined above I see that $requestData is always empty
and I got validations errors
*/
// Validations rules
$customerValidationRulesArray= Customer::getCustomerValidationRulesArray( $request->get('id'), ['status'] );
return $customerValidationRulesArray;
}
}
In routes/api.php defined :
Route::post('customer_register_store', 'CustomerRegisterController#store');
In the console of my bhrowser I see : https://imgur.com/a/0vsPIsa, https://imgur.com/a/wJEbBnP
I suppose that something is wrong in axios header ? without 'Content-Type' defined my validation rules work ok...
MODIFIED BLOCK #3
I managed to make fetch of blob with metod like :
var self = this;
fetch(this.previewCustomerRegister.avatarFile.blob) .then(function(response) {
console.log("fetch response::")
console.log( response )
if (response.ok) {
return response.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
// myImage.src = objectURL;
console.log("objectURL::")
console.log( objectURL )
console.log("self::")
console.log( self )
let customerRegisterArray =
{
username: self.previewCustomerRegister.username,
email: self.previewCustomerRegister.email,
first_name: self.previewCustomerRegister.first_name,
last_name: self.previewCustomerRegister.last_name,
account_type: self.previewCustomerRegister.account_type,
phone: self.previewCustomerRegister.phone,
website: self.previewCustomerRegister.website,
notes: self.previewCustomerRegister.notes,
avatar_filename: self.previewCustomerRegister.avatarFile.name,
avatar: objectURL,
};
console.log("customerRegisterArray::")
console.log(customerRegisterArray)
axios({
method: 'POST',
url: window.API_VERSION_LINK + '/customer_register_store',
data: customerRegisterArray,
// headers: {
// 'Content-Type': 'multipart/form-data' // multipart/form-data - as we need to upload with image
// }
}).then((response) => {
self.is_page_updating = false
self.message = ''
self.showPopupMessage("Customer Register", 'Customer added successfully ! Check entered email for activation link !', 'success');
alert( "SAVED!!::")
}).catch((error) => {
self.$setLaravelValidationErrorsFromResponse(error.response.data);
self.is_page_updating = false
self.showRunTimeError(error, this);
self.showPopupMessage("Customer Register", 'Error adding customer ! Check Details fields !', 'warn');
// window.grecaptcha.reset()
self.is_recaptcha_verified = false;
self.$refs.customer_register_wizard.changeTab(3,0)
});
});
} else {
return response.json().then(function(jsonError) {
// ...
});
}
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
In objectURL and self I see proper values : https://imgur.com/a/4YvhbFz
1) But checking data on server in laravel's control I see the same values I had at start of my attemps to upload image:
[avatar_filename] => patlongred.jpg
[avatar] => blob:http://local-hostels2.com/d9bf4b66-42b9-4990-9325-a72dc8c3a392
Have To manipulate with fetched bnlob in some other way ?
2) If I set :
headers: {
'Content-Type': 'multipart/form-data'
}
I got validation errors that my data were not correctly requested...
?
You're using request type as application/json hence you won't be able to save the image this way, for a file upload a request type should be multipart/form-data in this case you'll need to send request as
let customerRegisterArray = new FormData();
customerRegisterArray.put('username', this.previewCustomerRegister.username);
customerRegisterArray.put('email', this.previewCustomerRegister.email);
....
customerRegisterArray.put('avatar', this.previewCustomerRegister.avatarFile);
console.log("customerRegisterArray::")
console.log(customerRegisterArray)
axios({
method: ('post'),
url: window.API_VERSION_LINK + '/customer_register_store',
data: customerRegisterArray,
headers: {
'Content-Type': 'multipart/form-data'
}
}).then((response) => {
this.showPopupMessage("Customer Register", 'Customer added successfully !Check entered email for activation link !', 'success');
alert( "SAVED!!::"+var_dump() )
}).catch((error) => {});
Thank you for your help!
Valid decision was :
var self = this;
fetch(this.previewCustomerRegister.avatarFile.blob) .then(function(response) {
if (response.ok) {
return response.blob().then(function(myBlob) {
var objectURL = URL.createObjectURL(myBlob);
let data = new FormData()
data.append('username', self.previewCustomerRegister.username)
data.append('email', self.previewCustomerRegister.email)
data.append('first_name', self.previewCustomerRegister.first_name)
data.append('last_name', self.previewCustomerRegister.last_name)
data.append('account_type', self.previewCustomerRegister.account_type)
data.append('phone', self.previewCustomerRegister.phone)
data.append('website', self.previewCustomerRegister.website)
data.append('notes', self.previewCustomerRegister.notes)
data.append('avatar_filename', self.previewCustomerRegister.avatarFile.name)
data.append('avatar', myBlob)
axios({
method: 'POST',
url: window.API_VERSION_LINK + '/customer_register_store',
data: data,
headers: {
'Accept': 'application/json',
'Content-Type': 'multipart/form-data' // multipart/form-data - as we need to upload with image
}
}).then((response) => {
self.is_page_updating = false
self.message = ''
self.showPopupMessage("Customer Register", 'Customer added successfully ! Check entered email for activation link !', 'success');
alert( "SAVED!!::123")
// self.$router.push({path: '/'});
}).catch((error) => {
self.$setLaravelValidationErrorsFromResponse(error.response.data);
self.is_page_updating = false
self.showRunTimeError(error, this);
self.showPopupMessage("Customer Register", 'Error adding customer ! Check Details fields !', 'warn');
window.grecaptcha.reset()
self.is_recaptcha_verified = false;
self.$refs.customer_register_wizard.changeTab(3,0)
});
});
} else {
return response.json().then(function(jsonError) {
// ...
});
}
}).catch(function(error) {
console.log('There has been a problem with your fetch operation: ', error.message);
});
and common laravel's file uploading functionality :
$customerAvatarUploadedFile = $request->file('avatar');
...

Angularjs multiple ajax requests optimization

I am still learning Angular JS and have this controller which is making two ajax requests to the lastfm api using different parameters. I want to know when each request has been finished, so that I can display a loading indicator for both requests. I have researched it and read about promises and the $q service but cant get my head around how to incorporate it into this. Is there a better way to set this up? and how can I know when each request is done. Thanks.
angular.module('lastfm')
.controller('ProfileCtrl', function ($scope, ajaxData, usersSharedInformation, $routeParams) {
var username = $routeParams.user;
//Get Recent tracks
ajaxData.get({
method: 'user.getrecenttracks',
api_key: 'key would go here',
limit: 20,
user: username,
format: 'json'
})
.then(function (response) {
//Check reponse for error message
if (response.data.message) {
$scope.error = response.data.message;
} else {
$scope.songs = response.data.recenttracks.track;
}
});
//Get user info
ajaxData.get({
method: 'user.getInfo',
api_key: 'key would go here',
limit: 20,
user: username,
format: 'json'
})
.then(function (response) {
//Check reponse for error message
if (response.data.message) {
$scope.error = response.data.message;
} else {
$scope.user = response.data.user;
}
});
});
I have this factory which handles all the requests
angular.module('lastfm')
.factory('ajaxData', function ($http, $q) {
return {
get: function (params) {
return $http.get('http://ws.audioscrobbler.com/2.0/', {
params : params
});
}
}
});
Quite easy using $q.all(). $http itself returns a promise and $q.all() won't resolve until an array of promises are resolved
var ajax1=ajaxData.get(....).then(....);
var ajax2=ajaxData.get(....).then(....);
$q.all([ajax1,ajax2]).then(function(){
/* all done, hide loader*/
})

Resources