I have a picture, profile.jpg on my server. When I upload a new picture, replacing picture.jpg in data but not in name (in other words, the old profile.jpg gets replaced by the new profile.jpg, but the new one is also called profile.jpg). After my promise is returned, I call forceUpdate, but this doesn't do anything unless I change the actual url (src) of the image. See my code, in which I attempted to concatenate promises in order that react would recognize that the url is changing (from the correct url, to "empty", to the correct url again):
fetch('http://localhost:3000/change_pet_pic/?petID='+this.props.pet.id+'&userID='+this.props.pet.ownerID, { method: 'POST', body: form })
.then(function(res) {
return res.json();
}).then(function(json) {
var pet = $this.props.pet;
pet.petPicture = "empty";
$this.props.pet=pet;
$this.forceUpdate();
return json.picture_url;
}).then(function(url){
var pet = $this.props.pet;
pet.petPicture = url;
$this.props.pet=pet;
$this.forceUpdate();
})
Thanks for your tips!
It seems nothing wrong with your ReactJS code instead it should be a browser cache which is causing issue by returning the old image all the time as the image url looks same.
What you can do to get rid of this is, you can access the image with different query string whenever the image is getting changed.
So the first time, you can access this with profile.jpg?v=1 and the second time, you can access it with profile.jpg?v=2 something like that.
Related
I am trying to send a JSON file and an image file together to a server, but am really struggling.
1) If I send just the quilt item, so skipping the formData and changing the $http part below to $http.post('quilts/create/', quilt), then set the server end point to expect (#RequestBody QuiltRequest quiltRequest) without the bits about transformRequest and headers, it processes the data therein quite happily but I don't have an image to add to the records.
2) If I don't add the quilt item to the formData, and tell the server to expect (#RequestParam("image") MultipartFile image), I can save the image file on my server and generate a url string for it, but have no other quilt information to make the corresponding database entry.
How can I send both the quilt and the image in one request, and have the server receive and process both?
Many thanks!
Client-side service:
this.create = function (quilt, image) {
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
const formData = new FormData();
formData.append('quiltRequest', quilt);
formData.append('image', image);
$http.post('quilts/create/', formData, {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
};
Server-side end point:
#PostMapping(path = "/create")
public BigInteger create(#RequestPart QuiltRequest quiltRequest, #RequestPart MultipartFile image) throws IOException {
// do stuff based on parameters received
}
Apart of it, i think you can try to encode the image to base64 string. Send it to server and at the server, You decode it
My required solution have given by a real-world hero, and is posted here in case anyone else with a similar problem stumbles upon this thread :) (But thank you to user3562932 for taking some time to read and make a suggestion).
On the client side, we have moved the five lines of data preparation into a separate method, such that the original create() now takes a bunch of parameters and jumps straight to $http.post(url, data which has been magically transformed into something appropriate to send {rules on how to send the data}).
$http.post('quilts/create/', formData(quilt, image), {
transformRequest: angular.identity,
headers: {'Content-Type': undefined}
}).then(function (response) {
return window.location = '#!/quilts/created/' + response.data;
})
The magical transformation happens in new function formData(), which takes as its parameters the data we want to send and makes the necessary changes:
1) make a formData container for the data to be POSTed.
2) stringify information from the html form (e.g. text, numbers) into a JSON and append to formData.
2a) in this particular case, my quilt structure contains size and maker details which arrived from the backend as JSONs, and were selected in the webpage from drop-down lists of various sizes and makers, hence the parsing rows to get these items ready to be included in the formData.
3) convert files into BLOBs, and then likewise append.
4) return formData, with all required information neatly wrapped up and ready to go!
Note: in the services.js file, this formData() method actually appears above the create() method, but it feels more logical to talk about them this way around.
function formData(quilt, image) {
let formData = new FormData();
quilt.size = JSON.parse(quilt.size);
quilt.maker = JSON.parse(quilt.maker);
formData.append('quiltRequest', JSON.stringify(quilt));
formData.append('image', new Blob([image]));
return formData;
}
On the server side, we can now happily receive this through:
#PostMapping(path = "/create", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public BigInteger create(#RequestParam(value = "quiltRequest") String quiltRequest,
#RequestParam(value = "image") MultipartFile image) throws IOException {
QuiltRequest quilt = new ObjectMapper().readValue(quiltRequest, QuiltRequest.class);
QuiltResponse quiltResponse = quiltService.create(quilt, image);
return quiltResponse.getQuilt().getId();
}
In order to enable the end point to consume our exciting multimedia input, we have to add the following import at the top of the class:
import org.springframework.http.MediaType;
We use another import to enable the use of the MultipartFile class that we have designated for the incoming image file:
import org.springframework.web.multipart.MultipartFile;
The JSON object from the webpage has come through as a String, but that needs to be parsed into its underlying components to actually be of use. This is where the ObjectMapper comes into play. Call on its readValue() method, and pass in the string argument plus a template of what the information should look like when unwrapped (here, a QuiltRequest class with defined properties corresponding to the information we fed into the JSON back in the client-end server). Remember to include the necessary import to access the ObjectMapper:
import com.fasterxml.jackson.databind.ObjectMapper;
Hopefully this breakdown of the changes makes sense, with enough explanation to help other developers build end-to-end POST requests to suit their own projects.
Quick question, using the Baqend SDK in React, I'm saving profile images using the id of an object saved in the database as the name.
But in order to get the image to update in the user's browser after it is uploaded, I'm modifying state and adding &updated=true to the end of the file.url as it is returned by Baqend.
The save image code:
uploadLogo(event) {
event.preventDefault();
const name = this.props.match.params.id+"logo";
const file = event.target.files[0];
const img = new db.File({ name: name, data: file, type: 'blob' });
img.upload({force: true}).then((file) => {
db.Companies.load(this.props.match.params.id).then(company => {
this.setState({
logo: file.url+"?updated=true"
});
company.logo = file.url;
return company.update();
},
(error) => {
alert(error);
});
});
}
Is this the correct approach with React and the Baqend SDK? Are there going to be any side-effects on this if I'm loading a bunch of images by URL that look like this: https://remarkable-apple-95.app.baqend.com/v1/file/www/cce9830b-48eb-422e-830d-72ae28571480logo?BCB&updated=true
I would imagine url parameters like this are just ignored? The only person that is going to load the image with ?updated=true after it is the one person that updates the logo and only immediately after he updates it.
Also what is the ?BCB being added in file.url doing?
Your example looks good so far.
But you should not add additional query parameters at all, as they cause cache misses in the CDN.
The BCB (Baqend Cache Buster) is actually what you are trying to archive with the ?upload=true parameter. The SDK adds those cache busters automatically if an image was changed previously.
The BCB ensures that the fresh image is fetched from the server and is only cached with revalidation headers until the old image is expired in the browser cache. Our CDN caches are aware of this special cache buster and rewrite the image request back to the original URL to ensure cache hits in the CDN.
Note that our CDN caches are instantly invalidated if the content is changed.
This staleness information is propagated to other clients as well via a Bloom filter. That ensures that other clients won't take the image out of their local cache and therefore see the new image too.
Currently I am working on an angularJS app. it reads an property file from a repository and output an xeditable table, so you can change all the properties.
The only question I have now is, how do I make http.put request to update data.
assume the properties file have:
var data = {'prop1' : 'this', 'prop2' : 'that'};
I currently have:
$scope.update = function(key,value){
$http.post('http://blabla.com/properties.json', $scope.data)
.success(function (data, status) {
//success code
})
So Assume I updated the prop1 from this to newProperty.
Is my code correct? does the file in remote repository get updated?
or do i have to do something like this
$scope.update = function(key,value){
$http.post('http://blabla.com/properties.json',
data: {
prop1: 'newProperty'
})
.success(function (data, status) {
//success code
})
I only changed prop1, do I need to include prop2 also inside the data bracket? What happen when i got like 30 properties, then how do I update only 1 of the values using Http.put?
thanks
That URL won't work. You need a server-side API that can handle file uploads. That's not possible with Angular alone.
e.g.
$http.put('http://blabla.com/properties_api', $scope.data);
Also you've written $http.post up there, and that will (and should) fail unless the data doesn't exist in your repository. Change it to put if you're updating, 'post' only if you're creating.
Generally, uploading the full object is the way to go, otherwise the server is likely to treat the lack of other data as an explicit intention to delete that data. If you've got 30 properties, upload all 30.
That depends on how your server code handles this upload. If you write it yourself it'll do whatever you want with the data you give it.
That being said, if you've got a lot of data it can be unfeasible to upload it all, so you can create multiple APIs that can each handle a particular type of data.
e.g.
$http.put('http://blabla.com/properties_api/prop1_handler', $scope.data);
I need to save an attribute value to a variable to be sent to a later object to verify a picture I upload which gets a unique src id is accessible using the src attribute I obtain. Later a search through a list of pictures will be done to find that particular uploaded picture.
getPictureSrc: function() {
var el = $('img');
var uniqueId = el.getAttribute('src');
return uniqueId;
},
findPicture: function() {
browser.get('a different webpage then the upload page');
var findPic = getPictureSrc();
var allPics = $$('img');
helper.expectedConditions.isVisible(allPics.$(findPics));
},
However when I run this code, I do a console.log() and it throws back the list of available commands. Not the source. However if I do an expect against some random value the src does show. So I guess there are two questions, how do I print an attribute value to console and how do I pass an attribute value from object to object. Thank you.
However when I run this code, I do a console.log() and it throws back the list of available commands.
getPictureSrc() returns a promise. A promise is what you see printed on the console. If you need the actual value, resolve the promise explicitly:
getPictureSrc().then(function (src) {
console.log(src);
});
However if I do an expect against some random value the src does show.
This is the magic of the expect() - it is patched (by jasminewd package) to implicitly resolve promises before making an expectation. Having expect() accepting promises is quite convenient.
I am using Angular js to show loading screen. It works for all the REST services call except REST service to download the file. I understand why it is not working because for download I am not making any service call using $resource; instead of that I am using normal approach to download the file therefore Angular js code doesn't have any control on start/finish the service request. I tried to use $resource to hit this REST service however I am getting the data from this service and in this case loading screen was working fine however not sure how to use this data to display to user to download in angular way. Following are required details. Please help.
Approach 1 using iframe approach:
/*Download file */
scope.downloadFile = function (fileId) {
//Show loading screen. (Somehow it is not working)
scope.loadingProjectFiles=true;
var fileDownloadURL = "/api/files/" + fileId + "/download";
downloadURL(fileDownloadURL);
//Hide loading screen
scope.loadingProjectFiles=false;
};
var $idown; // Keep it outside of the function, so it's initialized once.
var downloadURL = function (url) {
if ($idown) {
$idown.attr('src', url);
} else {
$idown = $('<iframe>', { id: 'idown', src: url }).hide().appendTo('body');
}
};
Approach 2 using $resource (Not sure how to display data on screen to download)
/*Download file */
scope.downloadFile = function (fileId) {
//Show loading screen (Here loading screen works).
scope.loadingProjectFiles=true;
//File download object
var fileDownloadObj = new DownloadFile();
//Make server call to create new File
fileDownloadObj.$get({ fileid: fileid }, function (response) {
//Q? How to use the response data to display on UI as download popup
//Hide loading screen
scope.loadingProjectFiles=false;
});
};
This is the correct pattern with the $resource service:
scope.downloadFile = function (fileId) {
//Show loading screen (Here loading screen works).
scope.loadingProjectFiles=true;
var FileResource = $resource('/api/files/:idParam', {idParam:'#id'});
//Make server call to retrieve a file
var yourFile = FileResource.$get({ id: fileId }, function () {
//Now (inside this callback) the response data is loaded inside the yourFile variable
//I know it's an ugly pattern but that's what $resource is about...
DoSomethingWithYourFile(yourFile);
//Hide loading screen
scope.loadingProjectFiles=false;
});
};
I agree with you that this is a weird pattern and is different of other APIs where the downloaded data is assigned to a parameter in a callback function, hence your confusion.
Pay attention to the names and the cases of the parameters, and look that there're two mappings involved here, one between the caller to the $resource object and the object itself, and another between this object and the url that it contructs for downloading the actual data.
Here are some idea's for the second approach, you could present the user with a link after the download has happened:
With a "data url". Probably not a good idea for large files.
With a URL like "filesystem:mydownload.zip" You'd first have to save the file with the filesystem API. You can find some inspiration on html5rocks