Skicka formulär med JavaScript (XMLHttpRequest)

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.

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

Lämna ett svar

E-postadressen publiceras inte. Obligatoriska fält är märkta *