zhang_career zhang_career - 1 month ago 5
Ajax Question

PATCH request does not work on blur, while does on keydown

I am trying to create an inline editor which will support editing on click and saving on enter/blur. The environment includes PHP v5.6, jQuery v3.1, Laravel v5.2 and MySQL.

The question is, while saving with enter works, the saving on blur does not.

Laravel DB:listen to saving on enter:

2016-08-10 12:01:45: select * from `user` where `user`.`id` = '15' limit 1
2016-08-10 12:01:45: update `user` set `name` = '22222', `updated_at` = '2016-08-10 12:01:45' where `id` = '15'


Laravel DB:listen to saving on blur, note that there is no "update" query at all:

2016-08-10 11:21:53: select * from `user` where `user`.`id` = '15' limit 1


It seems the blur does not detect that the
<input>
text has changed. How should I solve it?

inline-edit.js
:

var edit = $(".inline-edit");

edit.click(function() {
$('.ajax').html($('.ajax input').val());
$('.ajax').removeClass('ajax');
$(this).addClass('ajax');
$OLDVAL = $.trim($(this).text());
$(this).html('<input id="inline-editbox" type="text" value="' + $OLDVAL + '">');
// focus and move cursor to the end of the editbox
var inline_editbox = $('#inline-editbox');
inline_editbox.focus();
var editbox_value = inline_editbox.val();
inline_editbox.val('');
inline_editbox.val(editbox_value);
//
inline_editbox.blur(function(event) {
var formData = {
tag: $(this).attr('id'),
value: $('.ajax input').val(),
};
$.ajax({
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
},
type: "PATCH",
data: formData,
dataType: 'json',
success: function(data) {
$('.ajax').html($('.ajax input').val());
$('.ajax').removeClass('ajax');
},
error: function(xhr, status, error) {
console.warn(xhr.responseText);
alert(error);
}
});
});
});

edit.keydown(function(event) {
if (event.keyCode == 13) {
var formData = {
tag: $(this).attr('id'),
value: $('.ajax input').val(),
};
$.ajax({
headers: {
'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
},
type: "PATCH",
data: formData,
dataType: 'json',
success: function(data) {
$('.ajax').html($('.ajax input').val());
$('.ajax').removeClass('ajax');
},
error: function(xhr, status, error) {
console.warn(xhr.responseText);
alert(error);
}
});
}
});


Part of Laravel route.php:

Route::patch ('user/{id}', ['as'=>'user.patch', 'uses'=>'UserController@update_ajax']);


Part of Laravel UserController.php:

function update_ajax($id, UserCreateFormRequest $request)
{
$user = User::findOrFail($id);
$tag = $request->get('tag');
if( $tag == 'name') {
$user->update([
'name' => $request->get('value')
]);
}

if( $tag == 'email') {
$user->update([
'email' => $request->get('value')
]);
}

if( $tag == 'isRegistered') {
$user->update([
'isRegistered'=> $request->get('value')
]);
}

return response()->json([
'status' => 'success',
'msg' => 'Data created successfully',
]);
}

Answer

Your main problem seems to be that you listen to the keydown event on the wrong element (on the .inline-editbox instead of on the <input>).

However, there also is a lot of duplication and unnecessary steps in your code, so I have rewritten it to be clearer.

The following:

  • has a separate function to do the Ajax call (which makes the Ajax calls and the rest of the code easier to understand and, more importantly, easier to test)
  • uses jQuery's XHR promises to enable separation of application logic and Ajax logic
  • makes use of jQuery custom events via .trigger() so that the data upload to the server can be centralized in one spot
  • restores the original value when the server update fails (you can implement other error behavior)
  • does not send an update the server when the value has not changed
  • handles HTML special characters (quotes, angle brackets) properly (your version has a bug in this regard)

Full code:

function patchUser(params) {
    return $.ajax({
        url: 'user/...',
        headers: {
            'X-CSRF-TOKEN': $('meta[name="_token"]').attr('content')
        },
        type: "PATCH",
        data: JSON.stringify(params),
        contentType: "application/json"
    }).fail(function(xhr, status, error) {
        console.warn(xhr.responseText);
        alert(error);
    });
}

$(".inline-edit").click(function () {
    var $inlineEdit = $(this),
        curentValue = $.trim($inlineEdit.text());

    $inlineEdit.empty().addClass('ajax');
    $('<input>')
        .val(curentValue)
        .appendTo($inlineEdit)
        .on("blur", function () {
            $(this).trigger("contentchange");
        })
        .on("keydown", function (e) {
            if (e.keyCode == 13) $(this).trigger("contentchange");
        })
        .on("contentchange", function () {
            var newValue = $(this).val();
            if (newValue === curentValue) {
                $inlineEdit
                    .text(curentValue)
                    .removeClass('ajax');
            } else {
                patchUser({
                    tag: $inlineEdit.attr('id'),
                    value: newValue
                }).done(function (data) {
                    $inlineEdit.text(data);
                }).fail(function () {
                    $inlineEdit.text(curentValue);
                }).always(function () {
                    $inlineEdit.removeClass('ajax');
                });
            }
        })
        .focus();
});

Live sample

Click to run a demonstration. I replaced patchUser() with a mock-up here.

function patchUser(params) {
    var result = $.Deferred();
    result.resolve("Value saved: (" + params.value + ")");
    return result.promise();
}

$(".inline-edit").click(function () {
    var $inlineEdit = $(this),
        curentValue = $.trim($inlineEdit.text());

    $inlineEdit.empty().addClass('ajax');
    $('<input>')
        .val(curentValue)
        .appendTo($inlineEdit)
        .on("blur", function () {
            $(this).trigger("contentchange");
        })
        .on("keydown", function (e) {
            if (e.keyCode == 13) $(this).trigger("contentchange");
        })
        .on("contentchange", function () {
            var newValue = $(this).val();
            if (newValue === curentValue) {
                $inlineEdit
                    .text(curentValue)
                    .removeClass('ajax');
            } else {
                patchUser({
                    tag: $inlineEdit.attr('id'),
                    value: newValue
                }).done(function (data) {
                    $inlineEdit.text(data);
                }).fail(function () {
                    $inlineEdit.text(curentValue);
                }).always(function () {
                    $inlineEdit.removeClass('ajax');
                });
            }
        })
        .focus();
});
.inline-edit > input {
  width: 100%;
}
.ajax {
  border: 1px solid green;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div class="inline-edit" id="name">User Name</div>
<div class="inline-edit" id="email">User Email</div>
<div class="inline-edit" id="isRegistered">User IsRegistered</div>