I'm trying to follow this guide to update a form field when the user change another field.
I've correctly setup my FormTypes, but I'm having trouble submitting the form in Ajax without JQuery.
I have 2 select :
const blockchain = document.getElementById('strategy_farming_blockchain');
const dapp = document.getElementById('strategy_farming_dapp');
const csrf = document.getElementById('strategy_farming__token');
The blockchain field is supposed to update the dapp field.
If I submit the whole form, it's working :
blockchain.addEventListener('change', function () {
const form = this.closest('form');
const method = form.method;
const url = form.action;
var request = new XMLHttpRequest();
request.open(method, url, true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.onload = function () {
if (this.status >= 200 && this.status < 400) {
//Success
const html = new DOMParser().parseFromString(this.response, 'text/html');
dapp.innerHTML = html.querySelector('#strategy_farming_dapp').innerHTML;
} else {
//Error from server
console.log('Server error');
}
};
request.onerror = function () {
//Connection error
console.log('Connection error');
};
request.send(new FormData(form));
});
But I'm not supposed to submit the whole form, I'm supposed to submit only the blockchain value
I tried a lot of things, like
var formdata = new FormData(form);
formdata.delete(dapp.name);
request.send(formdata);
// It's working for a new entity, but if I'm editing one, it's not updating the dapp field...
or
var formdata = new FormData();
formdata.append(this.name, this.value);
formdata.append(csrf.name, csrf.value);
request.send(formdata);
// It's working in a NEW action, but not in an EDIT action...
or
var data = {};
data[this.name] = this.value;
request.send(data);
//or
request.send(JSON.stringify(data));
//If I dump($request->request) in the controller, it seems like there's no data...
//Or the request isn't parsed correctly, or there's something missing ?
I also tried with encodeURIComponent...
I'm out of ideas... Any ideas ? Thanks !
So I chose to use FormData and remove the dapp field.
const blockchain = document.getElementById('strategy_farming_blockchain');
const dapp = document.getElementById('strategy_farming_dapp');
blockchain.addEventListener('change', function () {
const form = this.closest('form');
const method = form.method;
const url = form.action;
var request = new XMLHttpRequest();
request.withCredentials = true;
request.open(method, url, true);
request.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
request.onload = function () {
if (this.status >= 200 && this.status < 400) {
//Success
const html = new DOMParser().parseFromString(this.response, 'text/html');
dapp.innerHTML = html.querySelector('#strategy_farming_dapp').innerHTML;
} else {
//Error from server
console.log('Server error');
}
};
request.onerror = function () {
//Connection error
console.log('Connection error');
};
var formdata = new FormData(form);
formdata.set(dapp.name, "");
request.send(formdata);
});
Here's the FormType
public function buildForm(FormBuilderInterface $builder, array $options): void
{
$builder
//...
->add('blockchain', EntityType::class, [
'required' => false,
'class' => Blockchain::class,
'attr' => ['class' => 'js-select2'],
]);
$formModifier = function (FormInterface $form, Blockchain $blockchain = null) {
$dapps = null === $blockchain ? [] : $blockchain->getDapps();
$form->add('dapp', EntityType::class, [
'class' => Dapp::class,
'required' => true,
'choices' => $dapps,
'placeholder' => 'My placeholder',
'attr' => ['class' => 'js-select2'],
]);
};
$builder->addEventListener(
FormEvents::PRE_SET_DATA,
function (FormEvent $event) use ($formModifier) {
/**
* #var StrategyFarming $data
*/
$data = $event->getData();
$blockchain = $data->getDapp() ? $data->getDapp()->getBlockchain() : null;
$formModifier($event->getForm(), $blockchain);
}
);
$builder->get('blockchain')->addEventListener(
FormEvents::POST_SUBMIT,
function (FormEvent $event) use ($formModifier) {
$blockchain = $event->getForm()->getData();
$formModifier($event->getForm()->getParent(), $blockchain);
}
);
}
In order for this to work, I had to add the blockchain field to my Form's Entity, so that the Request handle the field :
/**
* Not persisted
* #var Blockchain
*/
private $blockchain;
public function getBlockchain(): ?Blockchain
{
if ($this->blockchain === null && $this->dapp !== null && $this->dapp->getBlockchain() !== $this->blockchain) {
$this->blockchain = $this->dapp->getBlockchain();
}
return $this->blockchain;
}
public function setBlockchain(?Blockchain $blockchain): self
{
$this->blockchain = $blockchain;
return $this;
}
Related
When I write my functions as below, you can see that there is a record as seen in the image. Where exactly is the problem?
Which is the function that records like in the image?
The image saves as I want, but it saves to the database as in the image.
I did according to the answer in How to use custom upload adapter on ASP.Net Core - CKEDITOR 5? but I can't solve the problem
!!How can I give this.url to src?
class MyUploadAdapter
{
constructor(loader) {
// The file loader instance to use during the upload.
this.loader = loader;
this.urls = '/tr/UnitType/DocUploadImage';
}
// Starts the upload process.
upload() {
return this.loader.file.then(file => new Promise((resolve, reject) => {
this._initRequest();
this._initListeners(resolve, reject, file);
this._sendRequest(file);
}));
}
// Aborts the upload process.
abort() {
if (this.xhr) {
this.xhr.abort();
}
}
_initRequest() {
const xhr = this.xhr = new XMLHttpRequest();
xhr.open('POST', this.urls, true);
xhr.responseType = 'json';
}
// Initializes XMLHttpRequest listeners.
_initListeners(resolve, reject, file) {
const xhr = this.xhr;
const loader = this.loader;
const genericErrorText = `Couldn't upload file: ${file.name}.`;
xhr.addEventListener('error', () => reject(genericErrorText));
xhr.addEventListener('abort', () => reject());
xhr.addEventListener('load', () => {
const response = xhr.response;
if (!response || response.error) {
return reject(response && response.error ? response.error.message : genericErrorText);
}
resolve({
default: response.urls
});
});
if (xhr.upload) {
xhr.upload.addEventListener('progress', evt => {
if (evt.lengthComputable) {
loader.uploadTotal = evt.total;
loader.uploaded = evt.loaded;
}
});
}
}
// Prepares the data and sends the request.
_sendRequest(file) {
// Prepare the form data.
const data = new FormData();
data.append('upload', file);
this.xhr.send(data);
}
}
function MyCustomUploadAdapterPlugin(editor) {
editor.plugins.get('FileRepository').createUploadAdapter = (loader) => {
return new MyUploadAdapter(loader);
};
}
public async Task<JsonResult> DocUploadImage()
{
try
{
var config = new ConfigurationBuilder().AddJsonFile("appsettings.json").Build();
var webRootPath = config["AppSettings:urunResimPath"].ToString();
var uploads = Path.Combine(webRootPath, "assets");
var filePath = Path.Combine(uploads, "rich-text");
var urls = new List<string>();
//If folder of new key is not exist, create the folder.
if (!Directory.Exists(filePath)) Directory.CreateDirectory(filePath);
foreach (var contentFile in Request.Form.Files)
{
if (contentFile != null && contentFile.Length > 0)
{
await contentFile.CopyToAsync(new FileStream($"{filePath}\\{contentFile.FileName}", FileMode.Create));
urls.Add($"{HttpContext.Request.Host}/rich-text/{contentFile.FileName}");
}
}
return Json(urls);
}
catch (Exception e)
{
return Json(new { error = new { message = e.Message } });
}
}
I tried to print the countries using US Dollar in Restcountries API (https://restcountries.com/v3.1/all). I tried but nothing shows as an output:
My code as follows
const getUSDollar = () => {
const xhr = new XMLHttpRequest();
xhr.open("GET", "https://restcountries.com/v3.1/all", true);
xhr.responseType = "json";
xhr.onload = () => {
const data = xhr.response;
const datas = data.filter((value) => {
for (var i in value.currencies.name.USD) {
if (i === "Unites States Dollar") {
return true;
}
}
}).map((value) => value.name);
console.log(datas);
}
xhr.send();
};
getUSDollar();
Please look into this.
Can you try this
xhr.onload = () => {
const data = xhr.response;
const datas = data
.filter((item) => item?.currencies?.USD?.name === "United States dollar")
.map((value) => value.name);
console.log('datas', datas);
};
As #eglease suggested, you don't need to check the name, you can just check for USD and filter the obj like this,
xhr.onload = () => {
const data = xhr.response;
const datas = data
.filter((item) => item?.currencies?.USD)
.map((value) => value.name);
console.log('datas', datas);
};
It worked for me. Try and comment whether this is what you are expected or not?
I've been trying to work on this for a while. I'm working with google drive api -> and I'm trying to try to get the main script to re-run the request of the accessToken is incorrect and causes an error.
Can that be sent to the main script somehow?
Adding some code to show I've actually worked on this lol - left out some bc it's alot of other unrelated stuff.
I am using IndexedDB to pass info between SW and main Script
//////////////////////////////////////////////////////////////
//////////////// Check Database onload ///////////////////////
//////////////////////////////////////////////////////////////
window.addEventListener("load", checkUpload(), false);
function checkUpload() {
if (supportCheck()) {
let openRequest = indexedDB.open("GoogleDrive", 1);
openRequest.onsuccess = (e) => {
var db = e.target.result;
var objectStore = db
.transaction(["backups"], "readwrite")
.objectStore("backups");
var request = objectStore.get("1");
request.onerror = function () {
// Handle errors!
};
request.onsuccess = function (event) {
var data = event.target.result;
if (googleSignin.isAuthorizedForGDrive()) {
// Call SW Function
}
else {
//Google Sign in Error
}
let accessToken = gapi.auth.getToken().access_token;
data.access = accessToken;
// Put this updated object back into the database.
var requestUpdate = objectStore.put(data);
requestUpdate.onerror = function (event) {
// Do something with the error
};
requestUpdate.onsuccess = function (event) {
// Success - the data is updated!
// Call SW Function
};
}
}
}
}
}
//////////////////////////////////////////////////////////////
//////////////// Initialize Database Function ///////////////
//////////////////////////////////////////////////////////////
uploadBtn.addEventListener("click", handleUploadClick, false);
save.addEventListener("click", handleUploadClick, false);
//Adds/Create Data that is stored in IndexedDB so that the Service Worker can
access and use it
//ServiceWorker Call Function
function initSW() {
console.log("Script: Called InitSW()");
if ("serviceWorker" in navigator) {
navigator.serviceWorker
.register("serviceWorker.js")
.then((registration) => navigator.serviceWorker.ready)
.then((registration) => {
registration.sync.register("sendFile-sync").then(() => {
//Do Function using sync
try {
console.log("Script : Sync Registered");
} catch {
console.log("Script : Sync Not Registered");
}
});
});
}
}
SW
self.addEventListener("sync", (e) => {
if (e.tag === "sendFile-sync") {
console.log("SW Sync : Sync Found!");
e.waitUntil(fetchFile());
} else {
console.log("SW Sync : No Sync Found");
}
});
//Function Called above when sync is fired
function fetchFile() {
let openRequest = indexedDB.open("GoogleDrive", 1);
openRequest.onerror = function () {
};
openRequest.onsuccess = function () {
let db = openRequest.result;
let transaction = db.transaction(["backups"], 'readwrite');
let backups = transaction.objectStore("backups");
let request = backups.get("1");
request.onsuccess = function (event) {
let date = Date();
let accessToken = request.result.access;
console.log("SW Sync: Access Token - " + accessToken);
let BlobContent = request.result.text;
let file = BlobContent;
let metadata = {
name: "Backup " + date, // Filename
mimeType: "application/pdf", // mimeType at Google Drive
parents: ["root"], // Root Folder ID for testing
};
let form = new FormData();
form.append(
"metadata",
new Blob([JSON.stringify(metadata)], { type: "application/json" })
);
form.append("file", file);
fetch(
"https://www.googleapis.com/upload/drive/v3/files?uploadType=multipart&fields=id",
{
method: "POST",
headers: new Headers({ Authorization: "Bearer " + accessToken }),
body: form,
}
)
.then((res) => {
return res.json();
})
.then(function (val) {
console.log(val.error.message);
<!-- message is "invalid credentials --> in our scenario
}
})
}
request.onerror = function () {
console.log("SW Sync : Getting IndexedDB values error");
};
};
}
Sure, the ServiceWorker's clients are exposed in its self.clients property, from where you can find the correct Client with which you can communicate thanks to its postMessage() method.
How to find the correct client will depend on the situation, for instance in the install or activate events there should be only one client, so you should be able to reach it by doing
const clients = await self.clients.matchAll();
const client = clients[0];
client.postMessage("something bad happenned...");
In a fetch event, the clientId is exposed on the event instance, so you can do
const client = await self.clients.get(evt.clientId);
client.postMessage("something bad happenned...");
I must admit I don't know well the BackgroundSync API, so I'm not sure if in this sync event your page would be the only one client, however, you can certainly make your page open a private communication channel with the SW even before, which by the way, sounds like a better mean of passing your API's credentials than through IDB:
const channel = new MessageChannel();
channel.port1.onmessage = SWTalksToMe; // handle messages from SW
navigator.serviceWorker
.register("serviceWorker.js")
.then((registration) => navigator.serviceWorker.ready)
.then((registration) => {
registration.postMessage("", [channel.port2]));
return registration.sync.register("sendFile-sync")
})
//...
And in your ServiceWorker
self.addEventListener("message", evt => {
if(evt.ports) {
client_port = evt.ports[0];
}
});
Finally, if you wanted to communicate with all the clients, you could use a BroadcastChannel.
I have a code in which the "onloadend" runs after the "get" task, and the code would work the right way if "get" ran first. So i need a way to switch them. I must use HttpClient and JavaScript, I cannot use jQuery or anything else.
Here is my full code in CodePen: https://codepen.io/vargaadam19/pen/BaabVbZ?editors=0010
Here is the onloadend:
export class HttpClient {
constructor(url) {
this.url = url;
this.xhr = new XMLHttpRequest();
this.xhr.onloadend = (event) => {
return this.xhr.result;
};
}
I set header here:
setHeader() {
this.xhr.setRequestHeader('Content-Type','application/json');
}
and here is the get function:
get(async, header) {
return new Promise((resolve, reject) => {
this.xhr.open('GET', this.url, async);
this.xhr.setRequestHeader(header.name, header.value);
this.xhr.send();
resolve(this.xhr.response);`enter code here`
});
}
And here is the function that's what I invite the get function
getAll() {
this.httpClient.get(true, this.header).then((result) => {
const data = !!result ? result : '[]';
const items = JSON.parse(data);
console.log('result:', result);
items.forEach(item => {
const todo = new Todo(item.id, item.name, item.status);
this.items.push(todo);
});
});
}
How can I solve this problem about the order of the tasks?
I am trying to upload a file to web api which takes the file as byte array using angular 2 application.
I am not able to pass the byte array from angular 2 page to web api. It looks like the File Reader read method is asynchronous. How do I make this as synchronous call or wait for the file content to be loaded before executing the next line of code?
Below is my code
//attachment on browse - when the browse button is clicked
//It only assign the file to a local variable (attachment)
fileChange = (event) => {
var files = event.target.files;
if (files.length > 0) {
this.attachment = files[0];
}
}
//when the submit button is clicked
onSubmit = () => {
//Read the content of the file and store it in local variable (fileData)
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
this.fileData = fr.result; //Note : This always "undefined"
};
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: this.fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
//call the web api and pass the purchaseorder object
this.updatePoService
.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order)
.subscribe(data => {
if (data) {
this.saveSuccess = true;
}
else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
Any hint would be useful.
regards,
-Alan-
You cannot make this async call synchronous. But you can take advantage of the observables to wait for the files to be read:
//when the submit button is clicked
onSubmit = () => {
let file = Observable.create((observer) => {
let fr = new FileReader();
let data = new Blob([this.attachment]);
fr.readAsArrayBuffer(data);
fr.onloadend = () => {
observer.next(fr.result);
observer.complete()
};
fr.onerror = (err) => {
observer.error(err)
}
fr.onabort = () => {
observer.error("aborted")
}
});
file.map((fileData) => {
//build the attachment object which will be sent to Web API
let attachment: Attachment = {
AttachmentId: '0',
FileName: this.form.controls["attachmentName"].value,
FileData: fileData
}
//build the purchase order object
let order: UpdatePurchaseOrder = {
SendEmail: true,
PurchaseOrderNumber: this.form.controls["purchaseOrderNumber"].value,
Attachment: attachment
}
return order;
})
.switchMap(order => this.updatePoService.updatePurchaseOrder(this.form.controls["purchaseOrderRequestId"].value, order))
.subscribe(data => {
if (data) {
this.saveSuccess = true;
} else {
this.saveSuccess = false;
}
},
error => this.errors = error,
() => this.res = 'Completed'
);
}
I arrived here looking for a solution for a similar issue. I'm performing requests to an endpoint which can response a binary blob if anything goes well or a JSON file in event of error.
this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
if (err instanceof HttpErrorResponse) {
// here, err.error is a BLOB containing a JSON String with the error message
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
As FileReaderSync apparently doesn't work in Angular6 I took n00dl3's solution (above) to throw the error after parsing the Blob content:
return this.httpClient.post(urlService, bodyRequest,
{responseType: 'blob', headers: headers})
.pipe(map((response: Response) => response),
catchError((err: Error | HttpErrorResponse) => {
const message = `In TtsService.getTts(${locale},${outputFormat}). ${err.message}`;
if (err instanceof HttpErrorResponse) {
const $errBlobReader: Observable<HttpErrorResponse> = Observable.create((observer) => {
const fr = new FileReader();
const errorBlob = err.error;
fr.readAsText(errorBlob, 'utf8');
fr.onloadend = () => {
const errMsg = JSON.parse(fr.result).message;
const msg = `In TtsService.getTts(${locale},${outputFormat}). ${errMsg}`;
observer.error(ErrorDataService.overLoadError(err, msg));
};
fr.onerror = (blobReadError) => {
observer.error(blobReadError);
};
fr.onabort = () => {
observer.error('aborted');
};
});
return $errBlobReader;
} else {
return throwError(ErrorDataService.overLoadError(err, message));
}
}));
Thanks! You really saved my day!