I det här inlägget beskrivs hur du kan posta ett formulär med hjälp av ren JavaScript (Vanilla JS). JavaScript används för asynkron interaktion med servrar, du kan visa uppladdningsförlopp och visa animeringar medan förfrågningar skickas till en server.
Jag har använt JQuery för att skicka ajax-förfrågningar och för att få ajax-svar från servrar. Jag vill minska mitt beroende av JQuery och håller på att skriva om front-end-kod för att slutligen enbart förlita mig på ren JavaScript.
XMLHttpRequest (XHR) objekt används i JavaScript för att interagera med servrar. XMLHttpRequest kan användas för alla typer av data, det är inte bara XML som namnet antyder.
HTTP definierar flera metoder som beskriver åtgärden som avser att utföras på servern. GET, HEAD, POST, PUT, DELETE, TRACE, OPTIONS, CONNECT och PATCH är metoder som kan förekomma i servrars metoder. Många server metoder kan ha samma uri, en som hanterar GET och en som hanterar POST till exempel. En POST-förfrågan till en GET-metod är inte tillåten. HTTP-metoder utgör ett avtal mellan den anropande metoden och den som anropar.
Den här koden har testats och fungerar med Google Chrome (75.0.3770.100), Mozilla Firefox (67.0.4), Microsoft Edge (42.17134.1.0), detta utan någon polyfill. Koden fungerar i Internet Explorer (11.829.17134.0) med en polyfill för XMLHttpRequest. Om du vill stödja äldre webbläsare kan du läsa vårt inlägg om transpilering och komplettering av JavaScript.
Formulär
Detta formulär innehåller olika typer av inmatningskontroller och en förloppsindikator. Ett formulär med en filuppladdningskontroll måste skickas som multipart/form-data. En knapptypskontroll (button) används istället för en inlämningstyp (submit), detta för att formuläret inte skall skickas direkt till åtgärdsadressen (action).
@inject ICommonServices tools
@{
// Get form values
WebDomain current_domain = ViewBag.CurrentDomain;
UserDocument user = ViewBag.User;
KeyStringList tt = ViewBag.TranslatedTexts;
// Get translated texts
string register_account_tt = tt.Get("register-account");
string edit_tt = tt.Get("edit");
string user_details_tt = tt.Get("user-details");
string email_tt = tt.Get("email");
string password_tt = tt.Get("password");
string confirm_password_tt = tt.Get("confirm-password");
string public_name_tt = tt.Get("public-name");
string image_tt = tt.Get("image");
string upload_main_image_tt = tt.Get("upload-main-image");
string save_tt = tt.Get("save");
// Set the title for the page
if (user.user_email == "" && user.facebook_user_id == "")
{
ViewBag.Title = register_account_tt;
}
else
{
ViewBag.Title = edit_tt + " " + user_details_tt.ToLower();
}
// Set meta data
ViewBag.MetaTitle = ViewBag.Title;
ViewBag.MetaDescription = ViewBag.Title;
ViewBag.MetaKeywords = ViewBag.Title;
ViewBag.MetaCanonical = current_domain.web_address + "/user/edit";
ViewBag.MetaRobots = "noindex, follow";
// Set the layout for the page
Layout = "/Views/shared_front/_standard_layout.cshtml";
}
@*Title*@
<h1>@ViewBag.Title</h1>
<div class="annytab-basic-space"></div>
@*Menu*@
@await Html.PartialAsync("/Views/user/_user_menu.cshtml")
@*Edit form*@
<form id="inputForm" action="/user/edit" method="post" enctype="multipart/form-data">
@*Hidden data*@
@Html.AntiForgeryToken()
@*General information*@
<div class="annytab-top-form-container">
<input name="txtId" type="hidden" tabindex="-1" value="@user.id" />
<div class="annytab-form-label">@email_tt</div>
<input id="txtEmail" name="txtEmail" type="text" class="annytab-form-control" value="@user.user_email" placeholder="@email_tt" data-val="true"
data-val-required="@String.Format(tt.Get("error-field-required"), email_tt)" data-val-regex="@String.Format(tt.Get("error-field-invalid"), email_tt)"
data-val-regex-pattern="^.+[@@].+[.].+$" data-val-remote="@String.Format(tt.Get("error-field-invalid"), email_tt)"
data-val-remote-additionalfields="*.txtId,*.txtEmail" data-val-remote-url="/user/verify_email" />
<div class="field-validation-valid" data-valmsg-for="txtEmail" data-valmsg-replace="true"></div>
<div class="annytab-form-label">@password_tt</div>
<input name="txtPassword" type="password" class="annytab-form-control" value="" placeholder="@password_tt" />
<input name="txtConfirmPassword" type="password" class="annytab-form-control" placeholder="@confirm_password_tt" data-val="true"
data-val-equalto="@String.Format(tt.Get("error-field-confirm"), password_tt)" data-val-equalto-other="txtPassword" />
<div class="field-validation-valid" data-valmsg-for="txtConfirmPassword" data-valmsg-replace="true"></div>
<div class="annytab-form-label">@public_name_tt</div>
<input name="txtPublicName" type="text" class="annytab-form-control" value="@user.public_name" placeholder="@public_name_tt" />
</div>
<div class="annytab-basic-space"></div>
@*User image*@
<div class="annytab-top-form-container">
<div class="annytab-form-label">@(String.Format(upload_main_image_tt, "256 kb", "[jpg|jpeg|png|gif]"))</div>
<input name="uploadMediaFile" type="file" class="annytab-form-control annytab-form-upload" data-container-selector="#img0" data-val="true" data-val-file="@String.Format(tt.Get("error-upload-file"), "jpg|jpeg|png|gif", "262144")"
data-val-file-maxsize="262144" data-val-file-extensions="jpg|jpeg|png|gif" />
<div class="field-validation-valid" data-valmsg-for="uploadMediaFile" data-valmsg-replace="true"></div>
<div class="annytab-basic-space"></div>
<div id="img0">
@if (string.IsNullOrEmpty(user.image_url) == true)
{
<i class="fas fas fa-user-secret fa-6x fa-fw annytab-green-color"></i>
}
else
{
<img src="@(Tools.GetBlobStorageUrl() + "users/" + user.image_url)" alt="@image_tt" />
}
</div>
</div>
<div class="annytab-basic-space"></div>
@*Progress bar*@
<div style="display: block;position:relative;background-color: #6d6c6c;width: 100%;height: 40px;text-align: center;font-size: 16px;line-height: 40px;color: #ffffff;padding: 0; margin: 0;">
<div id="progress-bar" style="position:absolute;background-color: #4CAF50;width: 0;height: 40px;">
<span id="loading-text" style="position:center;">0%</span>
</div>
</div>
<div data-valmsg-summary="true">
<ul></ul>
</div>
@*Button panel*@
<div class="annytab-basic-button-container">
<input type="button" class="annytab-basic-button" value="@save_tt" onclick="sendForm()" />
<input type="button" class="annytab-basic-button" value="GetCustomInformation" onclick="getCustomInformation()" />
</div>
<div class="annytab-basic-space"></div>
</form>
Servermetod
Detta är den servermetoden som skall hantera en begäran, metoden har en samling av nyckel-värde-par som inparameter.
// Update user details
// POST: /user/edit
[HttpPost]
[ValidateAntiForgeryToken]
public async Task<IActionResult> edit(IFormCollection collection)
{
// Get the signed in user
Claim claim = ControllerContext.HttpContext.User.FindFirst("user");
UserDocument user = claim != null ? JsonConvert.DeserializeObject<UserDocument>(claim.Value) : null;
// Get the current domain
WebDomain current_domain = await this.web_domain_repository.GetCurrentDomain(ControllerContext.HttpContext);
// Get translated texts
KeyStringList tt = await this.static_text_repository.GetFromCache(current_domain.front_end_language_code);
// Create a new post if the user is null
if(user == null)
{
return Json(data: new ResponseData(false, "", String.Format(tt.Get("error-update-post"), tt.Get("user"))));
}
// Update values
user.user_email = collection["txtEmail"].ToString().StripHtml();
user.user_password = collection["txtPassword"].ToString() != "" ? PasswordHash.CreateHash(collection["txtPassword"].ToString()) : user.user_password;
user.public_name = collection["txtPublicName"].ToString().StripHtml();
// Make sure that the email is unique
ModelItem<UserDocument> user_on_email_model = await this.user_repository.GetByEmail(user.user_email);
if (user_on_email_model.item != null && user.id != user_on_email_model.item.id)
{
return Json(data: String.Format(tt.Get("error-field-unique"), tt.Get("email")));
}
// Get uploaded files
IFormFileCollection files = collection.Files;
// Loop all the images and save them
for (int i = 0; i < files.Count; i++)
{
// Just continue if the file is empty
if (files[i].Length == 0)
continue;
// Set the filename
string filename = user.id + Path.GetExtension(files[i].FileName);
// Delete the old image
if (user.image_url != "")
{
await this.blob_storage_repository.Delete("users", user.image_url);
}
// Add the blob
await blob_storage_repository.UploadFromStream("users", filename, files[i].OpenReadStream());
// Set the source
user.image_url = filename;
}
// Add or update the post
await this.user_repository.Upsert(user);
// Return a success response
return Json(data: new ResponseData(true, "", String.Format(tt.Get("success-post-updated"), tt.Get("user-details"))));
} // End of the edit method
Skicka med JavaScript
Det här är JavaScript-metoden som används för att skicka ett HTML-formulär till serverns metod. Hela formuläret läggs till i FormData-objektet.
function sendForm()
{
// Get variables
var form = document.getElementById('inputForm');
var progress_bar = document.getElementById('progress-bar');
var loading_text = document.getElementById('loading-text');
// Get form data
var form_data = new FormData(form);
// Post form data
var xhr = new XMLHttpRequest();
xhr.open('POST', form.getAttribute('action') , true);
xhr.onload = function ()
{
if (xhr.status === 200)
{
// Get the response
var data = JSON.parse(xhr.response);
// Check the success status
if (data.success === true)
{
// Output a success message
toastr['success'](data.message);
}
else
{
// Output error information
toastr['error'](data.message);
}
}
else
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
}
};
xhr.upload.addEventListener("progress", function (evt)
{
if (evt.lengthComputable)
{
var width = Math.round((evt.loaded / evt.total) * 100);
progress_bar.style.width = width + '%';
loading_text.innerHTML = width * 1 + '%';
}
}, false);
xhr.onerror = function ()
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
};
xhr.send(form_data);
} // End of the sendForm method
Svaret från servern är ett ResponseData objekt som ser ut så här.
public class ResponseData
{
#region variables
public bool success { get; set; }
public string id { get; set; }
public string message { get; set; }
public string url { get; set; }
#endregion
#region Constructors
public ResponseData(bool success, string id, string message, string url)
{
// Set values for instance variables
this.success = success;
this.id = id;
this.message = message;
this.url = url;
} // End of the constructor
#endregion
} // End of the class
Exempel, lägg till formulärdata.
var fd = new FormData();
fd.append('__RequestVerificationToken', document.getElementsByName('__RequestVerificationToken')[0].value);
fd.append('id', '123');
fd.append('filename', 'text.json');
fd.append('files[]', document.getElementById('uploadFileControl').files[0]);
Exempel, hämta data.
function getCustomInformation() {
var id = 'bbfe3671-e3f1-4c36-9a2b-1dae99bbfcae';
var lang = 'sv';
var xhr = new XMLHttpRequest();
xhr.open('GET', '/home/get_custom_information/' + id + "?lang=" + lang, true);
xhr.onload = function ()
{
if (xhr.status === 200)
{
// Get the response
var data = JSON.parse(xhr.response);
// Output a success message
toastr['success']("<b>" + data.title + "</b><br>" + data.description);
}
else
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
}
};
xhr.onerror = function ()
{
// Output error information
toastr['error'](xhr.status + " - " + xhr.statusText);
};
xhr.send();
} // End of the getCustomInformation method