Search code examples
javascriptphpajaxlaravellaravel-8

How to use Laravel codes as Ajax response


I'm using Laravel 8 and I tried using an Ajax request for showing the results of my table:

$.ajax({
    url: "{{url('/admin/users/order-by')}}",
    type: "POST",
    data: {
        order_by: orderBy,
        _token: '{{csrf_token()}}'
    },
    dataType: 'json',
    success: function (result) {
        var userList = document.getElementById("user-list");
        $('#contentDiv').show();
        $.each(result.orderby, function (key, value) {
            $("#user-list").append('<tr><td>' + value.id + '</td> <td>' + value.name + '</td> <td>' + value.email + '</td> <td>' + value.is_active + '</td><td class="d-flex"><div class="btn-group"></div></td></tr>');
        });
    }
});

So basically it will append some new rows to the table. And actually at the last <td class="d-flex"></td>, I wanted to use some buttons which goes like this:

@if(!$user->isSameUser($user->id))
    <td class="d-flex">
        <div class="btn-group">
            <a href="{{ route('admin.users.edit' , ['user' => $user->id]) }}" class="btn btn-sm btn-purple">Accesses</a>
            <a href="{{ route('admin.users.edit' , ['user' => $user->id]) }}" class="btn btn-sm btn-info">Money Wallet</a>
            <a href="{{ route('admin.users.edit' , ['user' => $user->id]) }}" class="btn btn-sm btn-warning"> View Information</a>
            <a href="{{ route('admin.users.edit' , ['user' => $user->id]) }}" class="btn btn-sm btn-navy">Edit Profile</a>
            <form id="delForm" action="{{ route('admin.users.destroy' , ['user' => $user->id]) }}" method="POST">
                @csrf
                @method('DELETE')
                <button type="button" class="button btn btn-sm btn-orange ml-1" onclick="this.classList.toggle('button--loading')" id="btn-submit" type="submit" data-name="{{ $user->name }}" >
                    <span class="button__text">Delete</span>
                </button>
            </form>
        </div>
    </td>
@endif

Now in order to show this data in Ajax response, I need to add this:

...<td class="d-flex"><div class="btn-group">__BUTTONS GOES HERS__</div></td></tr>');

But don't know how to concatenate the specified code with Ajax response.

Update 1

I finally could successfully pass the buttons to the table row.

But the only problem here is that it does not read the user id variable in JavaScript:

@push('scripts')
        $("#dropdown-list").on('change', function(){
            var orderBy = document.getElementById("dropdown-list").value;
            $('#contentDiv').hide();
            $("#user-list").html('');
            if(orderBy == 'asc')
            {
                var orderBy = 1;
            } else if(orderBy == 'desc')
            {
                var orderBy = 2;
            }

            const url1 = "{{ route('admin.users.permissions' , ['user' => '__USER_ID__']) }}"
            const url2 = "{{ route('admin.users.wallet' , ['user' => '__USER_ID__']) }}"
            const url3 = "{{ route('admin.users.info' , ['user' => '__USER_ID__']) }}"
            const url4 = "{{ route('admin.users.edit' , ['user' => '__USER_ID__']) }}"
            $.ajax({
                url: "{{url('/admin/users/order-by')}}",
                type: "POST",
                data: {
                    order_by: orderBy,
                    _token: '{{csrf_token()}}'
                },
                dataType: 'json',
                success: function (result) {
                    $('#contentDiv').show();
                    $.each(result.orderby, function (key, value) {
                        const $row = $('<tr />');
                        $row.append('<td>' + value.id + '</td>');
                        $row.append('<td>' + value.name + '</td>');
                        $row.append('<td>' + value.email + '</td>');
                        $row.append('<td>' + value.is_active + '</td>');
                        const $btn_grp = $('<td class="d-flex"><div class="btn-group" />');
                        $btn_grp.append('<a href="' + url1.replace('__USER_ID__', '{!! $user->id !!}') + '" class="btn btn-sm btn-purple">Permissions</a>');
                        $btn_grp.append('<a href="' + url2.replace('__USER_ID__', '1') + '" class="btn btn-sm btn-info">Wallet</a>');
                        $btn_grp.append('<a href="' + url3.replace('__USER_ID__', '1') + '" class="btn btn-sm btn-warning">Information</a>');
                        $btn_grp.append('<a href="' + url4.replace('__USER_ID__', '1') + '" class="btn btn-sm btn-navy">Edit</a>');
                        const $btn_close = $('</div></td></tr>');
                        $btn_grp.append($btn_close);
                        $row.append($btn_grp);
                        $("#user-list").append($row);
                    });
                }
            });
@endpush

@component('admin.layouts.content' , ['title' => 'Users List'])
...
@endcomponent

So as you can see I tried passing a user variable id like this:

$btn_grp.append('<a href="' + url1.replace('__USER_ID__', '{!! $user->id !!}') + '" class="btn btn-sm btn-purple">Permissions</a>');

But somehow I get Undefined variable: user error:

enter image description here

So what's going wrong here? How can I properly pass the variable?


Solution

  • One huge caveat I want to throw out up front, you need to be certain and handle your route permissions with policies or similar on the server

    There are a couple of different ways you can go about this.

    Server Side Resource

    This is the one I recommend. You would make an Eloquent API Resource and conditionally add the links you need to the user object. Ultimately, I recommend this one because you can utilize URL::signedRoute(...) to create a verifiable URL and help protect from someone tampering with the URL.

    <?php
     
    namespace App\Http\Resources;
     
    use Illuminate\Http\Resources\Json\JsonResource;
    use Illuminate\Support\Facades\URL;
     
    class UserResource extends JsonResource
    {
        /**
         * Transform the resource into an array.
         *
         * @param  \Illuminate\Http\Request  $request
         * @return array
         */
        public function toArray($request)
        {
            return [
                'id' => $this->id,
                'name' => $this->name,
                'email' => $this->email,
                'is_active' => $this->is_active,
                'links' => $this->when(Auth::id() === $this->id, function() {
                    return [
                        'Accesses' => URL::signedRoute('admin.users.edit' , ['user' => $this->id]),
                        'Money Wallet' => URL::signedRoute('admin.users.edit' , ['user' => $this->id]),
                        //etc
            ];
        }
    }
    

    Then, in your ajax request:

    $.ajax({
        url: "{{url('/admin/users/order-by')}}",
        type: "POST",
        data: {
            order_by: orderBy,
            _token: '{{csrf_token()}}'
        },
        dataType: 'json',
        success: function (result) {
            var userList = document.getElementById("user-list");
            $('#contentDiv').show();
            // Note the change from orderby to data here. Resources wrap their results in a data object by default.
            $.each(result.data, function(key, value){
                const $row = $('<tr />');
                $row.append('<td>' + value.id + '</td>');
                $row.append('<td>' + value.name + '</td>');
                $row.append('<td>' + value.email + '</td>');
                $row.append('<td>' + value.is_active + '</td>');
                const $btn_grp = $('<div class="btn-group" />');
                if(value.links){
                    // Object key is your button label and route is the value based on the resource above.
                    for(const [label, route] in Object.entries(value.links)){
                        $btn_grp.append('<a href="' + route + '" class="btn btn-sm btn-purple"> + label + </a>');
                    }
                }
                $row.append(
                    $('<td class="d-flex" />).append($btn_grp)
                );
                $("#user-list").append($row);
            });
        }
    });
    

    You can alternatively do the same directly in your controller by converting each user to an array and conditionally appending the links to that array before returning, but resources are built exactly for this sort of thing and help with separation of concerns.

    Client Side

    If you need to handle it strictly on the client side, this is how I would approach it. This method is a bit harder to maintain in my opinion, but it is a viable option. If you do go this route, you need to be absolutely certain that you protect the route(s) from unauthorized access on the server side since it would be trivial for a user to guess at the URL to other users' admin pages.

    const url = "{{ route('admin.users.edit' , ['user' => '__USER_ID__']) }}"
    $.ajax({
        url: "{{url('/admin/users/order-by')}}",
        type: "POST",
        data: {
            order_by: orderBy,
            _token: '{{csrf_token()}}'
        },
        dataType: 'json',
        success: function (result) {
            var userList = document.getElementById("user-list");
            $('#contentDiv').show();
            $.each(result.orderby, function (key, value) {
                const $row = $('<tr />');
                $row.append('<td>' + value.id + '</td>');
                $row.append('<td>' + value.name + '</td>');
                $row.append('<td>' + value.email + '</td>');
                $row.append('<td>' + value.is_active + '</td>');
                if(value.id === {{$user->id}}){
                    const $btn_grp = $('<div class="btn-group" />');
                    $btn_grp.append('<a href="' + url.replace('__USER_ID__', '{{$user->id}}') + '" class="btn btn-sm btn-purple">Accesses</a>');
                    $btn_grp.append('<a href="' + url.replace('__USER_ID__', '{{$user->id}}') + '" class="btn btn-sm btn-info">Money Wallet</a>');
                    //etc
    
                    const $btn_cell = $('<td class="d-flex" />');
                    $btn_cell.append($btn_grp);
                    $row.append($btn_cel);
                } else {
                    // I am assuming you will need something like this for rows that do not match
                    $row.append('<td class="d-flex"></td>');
                }
    
                $("#user-list").append($row);
            });
        }
    });