0
votes

I am trying to update an "article" which include title(text), body(textarea) and image(file). All goes well until i tried to implement uploading via ajax. Uploading image through regular Laravel means works great, but with ajax, i hit little snag. First, it refuses to validate file. Second if i comment-out validation for file in controller method, it goes "sort-of" well ... But then check $request->hasFile("image") doesn't recognize file at all ... so instead writes to db an default one("noimage.jpg"). My other ajax functions work great, only update causing problems.

Here is ajax function:

function ajaksUpdate(){
let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
console.log("hitUpdate");
//console.log();

let updateForm = document.getElementById("updateForm");

let urlId = window.location.href;
let getaArticleId = urlId.lastIndexOf("/");
let articleId = urlId.substring(getaArticleId+1, urlId.length);

let updateFormElements = {};
updateFormElements.title = updateForm.elements[3].value;
updateFormElements.body = CKEDITOR.instances.ckeditor.getData();//Ovo trece po redu je id polja sa ckeditorom.
updateFormElements.image = updateForm.elements[5].files[0];

//console.log();
/*var myformData = new FormData();        
myformData.append('title', updateFormElements.title);
myformData.append('body', updateFormElements.body);
myformData.append('image', updateFormElements.image);
let formData = $('#updateForm').serializeArray();
console.log(updateFormElements);*/
console.log("******");
/*for (var [key, value] of myformData.entries()) { 
    console.log(key, value);
}*/

$.ajax({

    url: '/updateAjax/'+articleId,
    enctype: 'multipart/form-data',
    type: 'POST',
    data: {_token: token , message: "bravo", articleId: articleId, title: updateFormElements.title, body: updateFormElements.body,image:updateFormElements.image},
    dataType: 'JSON',
    /*cache: false,
    contentType: false,
    processData: false,*/
    success: (response) => { 
        console.log("success");
        console.log(response);
    },
    error: (response) => {
        console.log("error");
        console.log(response);
    }
}); 

}

Here is controller method:

public function ajaxUpdate(Request $request)
    {

        if($request->ajax()){

            $article = Article::find($request->articleId);

            $validator = \Validator::make($request->all(), [
                "title" => "required",
                "body" => "required",
                'image' => 'image|nullable|max:1999'/*If commented, validation passes.*/
            ]);

            if ($validator->passes()){/*If validation passes, it cannot find 'image'*/
                $hasImage = false;
                //Handle file upload
                if($request->hasFile("image")){
                    $filenameWithExt = $request->file("image")->getClientOriginalName();
                    $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
                    $extension = $request->file("image")->getClientOriginalExtension();
                    $fileNameToStore = $filename."_".time().".".$extension;
                    $path = $request->file("image")->storeAs("public/images", $fileNameToStore);
                }
                else{
                    $fileNameToStore = "noimage.jpg";
                }

                $article->title = $request->input("title");
                $article->body = $request->input("body");
                //$article->user_id = auth()->user()->id;
                $article->image = $fileNameToStore;
                $article->save();


                $response = array(
                    'status' => 'success',
                    'msg' => "Hello!",
                    "request" => $request->all(),
                    "passesValidation" => true,
                    "article" => $article,
                    "hasImage" => $hasImage,
                );

                return response()->json($response);

            }
            else{

                $response = array(
                    'status' => 'success',
                    'msg' => "Hello!",
                    "request" => $request->all(),
                    "passesValidation" => false,
                );
                return response()->json($response);

            }

        }

    }

Edit1:

If validation(for image) in controller isn't commented out, it shows string like this: "C:\fakepath\855d671944d2c143ba672010acd04437.jpg". Json if isn't commented out:

{
    msg: "Hello!"
    passesValidation: false
    request:
    articleId: "3"
    body: "posttext"
    image: "C:\fakepath\855d671944d2c143ba672010acd04437.jpg"
    message: "bravo"
    title: "Post1"
    _token: "ZVZ9NDNOcMdgoJgvzYhR9LmrPfh7RfMiM1QJVk9v"
    __proto__: Object
    status: "success"
    __proto__: Object
}

If commented out, json look like this:

{
    article: {id: 3, title: "Post1", body: "<p><em>Lorem ipsum</em><strong> </strong>dolor sit…ctum. Duis feugiat facilisis lectus a cursus.</p>", created_at: "2019-06-18 00:23:25", updated_at: "2019-06-25 00:18:37", …}
    hasImage: false
    msg: "Hello!"
    passesValidation: true
    request:
    articleId: "3"
    body: "posttext"
    image: "C:\fakepath\855d671944d2c143ba672010acd04437.jpg"
    message: "bravo"
    title: "Post1"
    _token: "ZVZ9NDNOcMdgoJgvzYhR9LmrPfh7RfMiM1QJVk9v"
    __proto__: Object
    status: "success"
    __proto__: Object
}

Edit2:

I also tried:

let formData = new FormData();
formData.append("title", updateFormElements.title);
formData.append("body", updateFormElements.body);
formData.append("image", updateFormElements.image); 

and inserting and sending it. It throws "Illegal invocation at .... ". And for whatever reason, formData object is empty when i console.log(formData) it.

Edit3: Even if i add processData: false, in ajax call. But then it throws 419 error ...

Edit4:

I don't know if it's going to help, but my form is in modal, there is no submit button and button with ajaksUpdate() function is the one who submits/updates and it's outside of form. I'm updating with put method, also got csrf in modal's form.

Here is a image of how my form/modal looks like:

update_modal

Edit5:

As per @pal request: My html is formed dynamically, in another function which is called when document is loaded, in layouts blade:

function ajaksShow(){
    let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
    console.log("hit");
    //var n = str.lastIndexOf("planet");
    let urlId = window.location.href;
    let getaArticleId = urlId.lastIndexOf("/");
    let articleId = urlId.substring(getaArticleId+1, urlId.length);
    console.log(articleId);

    $.ajax({
        url: '/showAjax',
        type: 'POST',
        data: {_token: token , message: "bravo", articleId: articleId},
        dataType: 'JSON',
        success: (response) => { 
            console.log("success");
            console.log(response);
            let body = "";
            let imageStyle = ";height: 435px; background-position: center top; background-attachment: fixed; background-repeat: no-repeat;background-size:cover;";

            let img = response.article.image;
            let find = " ";
            let rep = new RegExp(find, 'g');
            img = img.replace(rep, "%20"); // class="alert alert-danger"
            let mymodalDelete = "<div class='modal' id='myModalDelete'><div class='modal-dialog'><div class='modal-content'><div class='modal-header'><h4 class='modal-title'>Do you really want to delete this article?</h4><button type='button' class='close' data-dismiss='modal'>&times;</button></div><div class='modal-body'>deleting ...</div><div class='modal-footer'><button class='btn btn-outline-danger' style='position: absolute;left:0px; margin-left: 1rem;' onclick='ajaksDelete(this)'>Delete</button><button type='button' class='btn btn-danger' data-dismiss='modal'>Close</button></div></div></div></div>";

            let updateForm = "<form method='POST' id='updateForm' enctype='multipart/form-data'><input type='hidden' name='_method' value='PUT'><input type='hidden' name='_token' value='"+token+"'><input id='' type='hidden' name='article_id' value='"+response.article.id+"' /><div class='form-group'><label class='label' for='title'>Title</label><input type='text' class='form-control' name='title' placeholder='Title' value='"+response.article.title+"' required></div><div class='form-group'><label for='body'>Body</label><textarea class='form-control' id='ckeditor' name='body' placeholder='Body' required>"+response.article.body+"</textarea></div><div class='form-group'><input type='file' name='image' id='image'></div></form>";
            let mymodalUpdate = "<div class='modal' id='myModalUpdate'><div class='modal-dialog'><div class='modal-content'><div class='modal-header'><h4 class='modal-title'>Do you really want to update this article?</h4><button type='button' class='close' data-dismiss='modal'>&times;</button></div><div class='modal-body'>"+updateForm+"</div><div class='modal-footer'><button class='btn btn-outline-success' style='position: absolute;left:0px; margin-left: 1rem;' onclick='ajaksUpdate()'>Update</button><button type='button' class='btn btn-info' data-dismiss='modal'>Close</button></div></div></div></div>";

            let imageUrl = "/storage/images/"+img;
            let html = "<a href='/list' class='btn btn-outline-info btn-sm'>Go Back</a><div class='nextPrev'><a href='/list/"+response.prev+"' class='btn btn-outline-success'><i class='fas fa-arrow-left'></i></a><a href='/list/"+response.next+"' class='btn btn-outline-info'><i class='fas fa-arrow-right'></i></a></div><br><br><div id='single-kv' style='background-image: url("+imageUrl+")"+imageStyle+";background-color: red !important;'></div><div id='single-intro'><div id='single-intro-wrap'><h1> "+response.article.title+"</h1>";

                if(response.article.body.length > 400){
                    body = response.article.body.substring(0, 400)+"<a id='readMore' href='/list/"+response.article.id+"'>...Read more</a>";
                }
                else{
                    body = response.article.body;
                }

                html += body;

                html += "<div class='comment-time excerpt-details' style='margin-bottom: 20px; font-size: 14px;'><a href='#gotoprofil'> "+response.user.name+" </a> - "+response.article.created_at+"</div><button id='update' class='btn btn-outline-info btn-sm float-left' data-toggle='modal' data-target='#myModalUpdate' onclick='getCkEditor()'>Update</button><button class='btn btn-outline-danger btn-sm float-right' data-toggle='modal' data-target='#myModalDelete'>Delete</button></div></div><br><hr style='color:whitesmoke; width: 50%;'><div id='single-body'><div id='single-content'>"+response.article.body+"</div></div>"+mymodalDelete+mymodalUpdate;


            if(document.getElementById("maine")){

                document.getElementById("maine").innerHTML = html;

            }

        },
        error: (response) => {
            console.log("error");
            console.log(response);
        }
    }); 

}

Form is updateForm variable and button that activate it, is in mymodalUpdate variable where it invokes function ajaksUpdate(). Also updateForm is chained in mymodalUpdate with two pluses on each side.

Edit6: I also tried:

let formData = $('#updateForm').serializeArray();
console.log(formData);

but it's only shows csrf, hidden method put field,token, title and body field. No file field?.

Edit7:

Here is two pics that'll(i hope) help to illustrate my point:

csrf1 and second csrf2

Edit8:

Somebody suggested it was a csrf mismatch concerning 419 error, so they suggested to temporary disable csrf to see what's what, here are pics:

Logged and middleware: loggedandmiddleware

and logged and controller method: loggedandcontrollermethod

Updated with change ajax function. If i add to ajax call

cache: false,
contentType: false,
processData: false,

then it show: "POST http://articleapp.test/updateAjax/3 419 (unknown status)" error. If comment this three lines out, then it shows: "Uncaught TypeError: Illegal invocation at add ..." error. I tried everything. and csrf ain't it because i tried disabling, it sends blank request.

Final edit: I don't know what i did, but file uploading works. Here is ajax function:

function ajaksUpdate(){
    let token = document.querySelector("meta[name='csrf-token']").getAttribute("content");
    console.log("hitUpdate");
    //console.log();

    let updateForm = document.getElementById("updateForm");

    let urlId = window.location.href;
    let getaArticleId = urlId.lastIndexOf("/");
    let articleId = urlId.substring(getaArticleId+1, urlId.length);

    let updateFormElements = {};
    updateFormElements.title = updateForm.elements[3].value;
    updateFormElements.body = CKEDITOR.instances.ckeditor.getData();//Ovo trece po redu je id polja sa ckeditorom.
    updateFormElements.image = updateForm.elements[5].files[0];

    let imginp = document.getElementById("imagex").files;
    //console.log(imginp);

    var myformData = new FormData();        
    myformData.append('title', updateFormElements.title);
    myformData.append('body', updateFormElements.body);
    myformData.append('image', updateFormElements.image);
    myformData.append('_token', token);
    myformData.append('articleId', articleId);
    //let formData = $('#updateForm').serializeArray();

    console.log("******");
    for (var [key, value] of myformData.entries()) { 
        console.log(key, value);
    }
    console.log("======");

    $.ajax({

        url: '/updateAjax/'+articleId,
        enctype: 'multipart/form-data',
        type: 'POST',
        data: myformData,
        dataType: 'JSON',
        cache: false,
        contentType: false,
        processData: false,
        success: (response) => { 
            console.log("success");
            console.log(response);
        },
        error: (response) => {
            console.log("error");
            console.log(response);
        }
    }); 

}

Here is controller method:

public function ajaxUpdate(Request $request)
    {

        if($request->ajax()){

            $article = Article::find($request->articleId);

            $validator = \Validator::make($request->all(), [
                "title" => "required",
                "body" => "required",
                'image' => 'image|nullable|max:1999'
            ]);

            if ($validator->passes()){
                $hasImage = false;
                //Handle file upload
                if($request->hasFile("image")){
                    $filenameWithExt = $request->file("image")->getClientOriginalName();
                    $filename = pathinfo($filenameWithExt, PATHINFO_FILENAME);
                    $extension = $request->file("image")->getClientOriginalExtension();
                    $fileNameToStore = $filename."_".time().".".$extension;
                    $path = $request->file("image")->storeAs("public/images", $fileNameToStore);
                }
                else{
                    $fileNameToStore = "noimage.jpg";
                }

                $article->title = $request->input("title");
                $article->body = $request->input("body");
                //$article->user_id = auth()->user()->id;
                $article->image = $fileNameToStore;
                $article->save();

                $response = array(
                    'status' => 'success',
                    'msg' => "Hello!",
                    "request" => $request->all(),
                    "passesValidation" => true,
                    "article" => $article,
                    "hasImage" => $hasImage,
                );

                return response()->json($response);

            }
            else{

                $response = array(
                    'status' => 'success',
                    'msg' => "Hello!",
                    "request" => $request->all(),
                    "passesValidation" => false,
                );
                return response()->json($response);

            }

        }

    }

Just want to leave this here for posterity and to ask others: Did you ever encountered problem, which miraculously resolved, but you've been none the wiser about solution whatever it may be?

3
You will have to gather the data in the javascript formData() function and send your image this way. As explained in this answer stackoverflow.com/a/10811427/2602434Dimitri Mostrey

3 Answers

0
votes

Well it seems that you're uploading the local path to your image instead of the image itself in the Ajax method. Reading this part of MDN documentation I realized you might write this:

updateFormElements.image = updateForm.elements[5].files[0];
0
votes

Instead of this

let formData = new FormData();
formData.append("title", updateFormElements.title);
formData.append("body", updateFormElements.body);
formData.append("image", updateFormElements.image); 

use this

var formData = new FormData(this);

Full source as below as you have generate from dynamically Please use event binding instead if directly give click events: Please check this out event binding

just give button like this: Submit button out side the from can be part of from check out this button outside form

<script>

    $(document).ready(function (e) {

      $('#updateBtn').on('submit',(function(e) {

        $.ajaxSetup({

          headers: {

             'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')

             }

            });

         e.preventDefault();

          var formData = new FormData(this);

           $.ajax({

                 type:'POST',

                  url: "{{ url('save-image')}}",

                  data:formData,

                  success:function(data){ },

                  error: function(data){}

            });

          }));

         });

0
votes

To upload file using ajax, try this:

var fileData = $('#id_of_file').prop('files')[0];

var formData = new FormData();  // Create formdata object
formData.append('fileData', fileData);  // append key: value pair in it
formData.append('key1', value1);
formData.append('key2', value2);

and use it in ajax like:

$.ajax({
    url: $('meta[name="route"]').attr('content') + '/ur_route',
    method: 'post',
    data: formData,
    contentType : false,
    processData : false,
    headers: {
        'X-CSRF-TOKEN': $('meta[name="csrf-token"]').attr('content')
    },
    success: function(response){
        // do whatever you want
    }
});

on server side (controller) you will get the file using fileData index.