Action code:
public class ContentAction extends ActionSupport {
private String menuName;
public String getMenuName() {
return menuName;
}
public void setMenuName(String menuName) {
this.menuName = menuName;
}
}
js code:
import "es6-promise/auto";
import fetch from 'isomorphic-fetch'
export function postAction(action,url,params=[]) {
return function (dispatch) {
const data = params;
return fetch(url,{
method:"POST",
body:data//data=Object{menuName:"index"}
})
.then(response => response.json())
.then(json =>dispatch(action(json)))
}
}
but when project run, menuName is null in action.
I try to use key-value pair:
import "es6-promise/auto";
import fetch from 'isomorphic-fetch'
export function postAction(action,url,params=[]) {
return function (dispatch) {
const data = Object.keys(params).map(key =>
encodeURIComponent(key)+"="+ encodeURIComponent(params[key])).join('&');
return fetch(url,{
method:"POST",
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:data//data="menuName=index"
})
.then(response => response.json())
.then(json =>dispatch(action(json)))
}
}
use JSON:
export function postAction(action,url,params=[]) {
return function (dispatch) {
const data = JSON.stringify(params);
return fetch(url,{
method:"POST",
headers:{'Content-Type':'application/json'},
body:data//data="{"menuName":"index"}"
})
.then(response => response.json())
.then(json =>dispatch(action(json)))
}
}
use FormData:
export function postAction(action,url,params=[]) {
return function (dispatch) {
const data = new FormData();
Object.keys(params).map(key =>
data.append(encodeURIComponent(key),encodeURIComponent(params[key])));
return fetch(url,{
method:"POST",
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:JSON.stringify(data)//data.get("menuName")="index"
})
.then(response => response.json())
.then(json =>dispatch(action(json)))
}
}
all of above can not work (menuName=null in action).
but if I append key-value pair to URL directly, it works (menuName=index in action):
import "es6-promise/auto";
import fetch from 'isomorphic-fetch'
export function postAction(action,url,params=[]) {
return function (dispatch) {
const data = Object.keys(params).map(key =>
encodeURIComponent(key)+"="+ encodeURIComponent(params[key])).join('&');
return fetch(url + "?" + data,{//url.action?menuName=index
method:"POST"
})
.then(response => response.json())
.then(json =>dispatch(action(json)))
}
}
but I think this way should not be standard,it is not even post. So Where did I make a mistake?
If you define a method:'POST' then a method is used is POST regardless of query string parameters.
You should use URLSearchParam to build the the string of parameters in x-www-form-urlencoded format.
const uparams = new URLSearchParams();
Object.keys(params).map(key =>
uparams.append(key, params[key]));
fetch(url,{
method:"POST",
headers:{'Content-Type':'application/x-www-form-urlencoded'},
body:uparams//data="menuName=index"
})
Related
I am trying to post a file object to my nuxt 3 api route
Problem is:
Data from client has my file object
Data from server returns empty object
Screenshot of the issue
Where did my file object go?
const handleImageUpload = async (evt: Event) => {
const target = evt.target as HTMLInputElement
if (target.files) {
const file = target.files[0]
const upload: iUpload = {
name: file.name,
type: file.type,
file
}
console.log("data from client", upload)
try {
const { data, error } = await useFetch(constants.imageUploadApiUrl, {
headers: { "Content-type": "application/json" },
method: 'POST',
body: upload
})
console.log("data from server", data.value)
} catch (error) {
console.log(error)
}
}
}
constants.imageUploadApiUrl (api route) has the following
import { getQuery, readBody } from "h3"
import { iUpload } from "~~/helpers/interfaces"
export default defineEventHandler(async (event) => {
try {
const query = getQuery(event)
const body = await readBody(event) as iUpload
return { body }
} catch (error: any) {
return { error: error.message }
}
})
iUpload interface is this
export interface iUpload {
name: string;
type: string;
file: File;
}
I eventually got it working. Meanwhile it's using supabase as it's backend (forgot to mention that).
But here are the changes I made.
#1 - I added a utility function to convert the file to base64 string
export const getBase64 = (file: File) => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result);
reader.onerror = error => reject(error);
});
}
#2 - I updated the handleImageUpload function like below. The only change being in the file key
const handleImageUpload = async (evt: Event) => {
const target = evt.target as HTMLInputElement
if (target.files) {
const fileObj = target.files[0]
const upload: iUpload = {
path: id(memberName(store.selected), '-'),
name: fileObj.name,
file: await getBase64(fileObj) as string, // <**=**
type: fileObj.type
}
console.log("data from client", upload)
try {
const { data, error } = await useFetch(constants.imageUploadApiUrl, {
headers: { "Content-type": "multipart/form-data" },
method: 'POST',
body: upload
})
console.log("data from server", data.value)
} catch (error) {
console.log(error)
}
}
}
#3 - Furthermore I updated the server route as follows:
export default defineEventHandler(async (event) => {
try {
const body = await readBody(event) as iUpload
const filePath = `${body.path}/${body.name}`
const res = await fetch(body.file)
const blob = await res.blob()
const response = await supabase.storage
.from("pictures")
.upload(filePath, blob, {
contentType: body.type,
upsert: true,
})
return {
data: response.data,
error: response.error?.message,
}
} catch (error: any) {
return { error: error.message }
}
})
#4 - Lastly I updated the policies on supabase storage bucket and storage object to the following:
supabase storage policy update
Property.entity.ts
#Column({ select: false })
address: string;
Property.service.ts
allWithLocation = async () => {
const properties = await this.repository
.createQueryBuilder("property")
.select("property")
.addSelect("property.address")
.getMany();
return properties;
};
Is there a way to write the code above like this using type-orm find options?
allWithLocation = async () => {
const properties = await this.repository.find({
addSelect: ["address"]
});
return properties;
};
Looks like you need to use "relations" property of FindOptionsRelations<T>.
Check here: https://orkhan.gitbook.io/typeorm/docs/find-options
allWithLocation = async () => {
const properties = await this.repository.find({
relations: {
address: true
}
});
return properties;
};
I would like my uploadFormPage() function to be able to take jpegs and pdf's. Is it possible for me to have 2 file types for the same FormData() const?`
export function uploadFormPage(documentId, formId, file, callback) {
return async dispatch => {
try {
const formData = new FormData();
formData.append('page', {
name: `document-${documentId}-${formId}-${Date.now()}.jpg`,
type: 'image/jpeg',
uri: file,
});
const result = await Api.uploadFiles(formData);
const entity = {
id: formId,
resourceKey: result.page,
};
const rsp = await Api.uploadFormPage(documentId, entity);
dispatch({type: LOAD_DOCUMENTS, data: rsp});
callback(null, rsp);
} catch (e) {
callback(e, null);
}
};
}
I'm trying to upload a file using apollo-server-express and apollo-client. However, when the file object is passed to the resolver it is always empty. I can see the file on the client, but not the server side. How can I resolve this ?
My Schema
type File {
id: ID
path: String
filename: String
mimetype: String
}
extend type Query {
getFiles: [File]
}
extend type Mutation {
uploadSingleFile(file: Upload!): File
}
My Resolver
Mutation: {
uploadSingleFile: combineResolvers(
isAuthenticated,
async (parent, { file }, { models, user, storeUpload }, info) => {
console.log('Resolver-> uploadSingleFile')
console.log(file) // Will return empty, { }
const x = await file
console.log(x) // Will also return empty, { }
const storedFile = storeUpload(file)
return storedFile
}
),
},
My Client-side queries file
export const UPLOAD_SINGLE_FILE = gql`
mutation uploadSingleFile($file: Upload!) {
uploadSingleFile(file: $file) {
id
}
}
`
My Client-side interface
import React from 'react'
// GQL
import { useApolloClient, useMutation } from '#apollo/react-hooks'
import { UPLOAD_SINGLE_FILE } from '../../queries'
const FileUpload = props => {
const [uploadSingleFile, uploadSingleFileResult] = useMutation(UPLOAD_SINGLE_FILE, {
onCompleted(uploadSingleFile) {
console.log('Completed uploadSingleFile')
}
})
const apolloClient = useApolloClient()
const handleUploadFile = ({
target: {
validity,
files: [file]
}
}) => {
console.log('Uploading file...')
if(validity.valid) {
console.log('Valid')
console.log(file.name)
uploadSingleFile({ variables: { file } })
.then(() => {
apolloClient.resetStore()
})
}
else console.log('Invalid file')
}
return(
<input type="file" required onChange={handleUploadFile} />
)
}
export default FileUpload
UPDATED
My front-end set-up is:
const httpLink = createHttpLink({
uri: 'http://localhost:4000/graphql',
})
const authLink = setContext((_, { headers }) => {
const token = localStorage.getItem('token')
return {
headers: {
...headers,
authorization: token ? `Bearer ${token}` : "",
}
}
})
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
You need to utilize the appropriate Link with your Apollo Client in order to enable file uploads. The easiest way to do that is by using createUploadLink from apollo-upload-client. It functions as a drop-in replacement for createHttpLink, so just swap out the functions and you'll be good to go.
const httpLink = createUploadLink({
uri: 'http://localhost:4000/graphql',
})
const authLink = ...
const client = new ApolloClient({
link: authLink.concat(httpLink),
cache: new InMemoryCache(),
})
Assuming you have the proper link set up and in use (using createUploadLink as Daniel mentions in his post), you should be able to destructure the props from file once you await the promise in your resolver on the server.
const { filename, mimetype, createReadStream } = await file.promise;
console.log(filename, mimetype);
// to get a stream to use of the data
const stream = createReadStream();
UPDATE: in more recent versions of graphql-upload you can just await the file like you do in the OP, rather than the file.promise. I was using an older version of the lib it seems.
I implement remote validation. It works for usual fields correctly, but if field is situated in kendowindow jquery validation does not work.
How can I solve this problem?
This can be achieved using Kendo UI validator as demonstrated below:
Model with "Remote" annotation attribute:
public class ProductViewModel
{
[Editable(false)]
public int ProductID { get; set; }
[Required]
[Remote("UniqueName", "Home", ErrorMessage = "The entered name already exists.")]
public string ProductName { get; set; }
}
Controller with "UniqueName" Action:
public ActionResult UniqueName(string productName)
{
var context = new NorthwindEntities();
return Json(!context.Products.Any(p => p.ProductName == productName), JsonRequestBehavior.AllowGet);
}
Script to add custom validation rule to the Kendo UI validation rules for the "Remote" validation attribute (can be placed anywhere on the page before Grid initialization code):
<script>
(function ($, kendo) {
$.extend(true, kendo.ui.validator, {
rules: {
//define custom validation rule to match remote validation:
mvcremotevalidation: function (input) {
if (input.is("[data-val-remote]") && input.val() != "") {
var remoteURL = input.attr("data-val-remote-url");
var valid;
$.ajax({
async: false,
url: remoteURL,
type: "GET",
dataType: "json",
data: validationData(input, this.element),
success: function (result) {
valid = result;
},
error: function () {
valid = false;
}
});
return valid;
}
return true;
}
},
messages: {
mvcremotevalidation: function (input) {
return input.attr("data-val-remote");
}
}
});
function validationData(input, context) {
var fields = input.attr("data-val-remote-additionalFields").split(",");
var name = input.prop("name");
var prefix = name.substr(0, name.lastIndexOf(".") + 1);
var fieldName;
var data = {};
for (var i = 0; i < fields.length; i++) {
fieldName = fields[i].replace("*.", prefix);
data[fieldName] = $("[name='" + fieldName + "']", context).val();
}
return data;
}
})(jQuery, kendo);
</script>
Grid initialization code:
#(Html.Kendo().Grid<KendoUIMVC5.Models.ProductViewModel>()
.Name("grid")
.Columns(columns =>
{
columns.Command(comm =>
{
comm.Edit();
});
columns.Bound(p => p.ProductID);
columns.Bound(p => p.ProductName);
})
.Pageable()
.Sortable()
.Filterable()
.DataSource(dataSource => dataSource
.Ajax()
.Model(model =>
{
model.Id(p => p.ProductID);
})
.Read(read => read.Action("Read", "Home"))
.Update(update => update.Action("Update", "Home"))
)
)