Search code examples
phpmysqlajaxlaravel-4long-polling

Long-polling in Laravel chat: Why is the div not updating itself?


I'm trying to build a laravel chat app with long-polling (I know there's nodejs/redis but there's a problem) and therefore I have been trying to implement this example into Laravel and MySQL.

However, the AJAX GET status request is always stuck at pending.... I figured this might be the cause that it is not updating the div where I display my new newly inputted value from an AJAX POST request on the same page. Currently, the div only updates itself on refresh.

Here are my codes :

View

function getMsg(timestamp){
    var queryString = {"timestamp":timestamp};
    $.get(
        "create",
        queryString,
        function(data){
            var obj = $.parseJSON(data);
            console.log(obj.timestamp);
            $('#response').append('<p>'+obj.body+'</p>');
            getMsg(obj.timestamp);
        }
    ).fail( function(xhr, textStatus, errorThrown) {
            alert(xhr.responseText);
        });
}
getMsg();

Controller

public function create()
{
    $Msg = new Chatting;
    if(Request::ajax()){
        set_time_limit(0);
        session_write_close();
        while(true){

            $last_ajax_call = isset($_GET['timestamp'])?(int)$_GET['timestamp']:null;

            clearstatcache();
            $last_timestamp = $Msg->select(array('created_at'))->orderBy('created_at','desc')->first();
            $last_change = json_decode($last_timestamp);
            if($last_ajax_call == null || $last_change->created_at > $last_ajax_call){
                $result = array(
                    'body'=> $Msg->select('body','created_at')->where('created_at','=',$last_change->created_at)->first()->body,
                    'timestamp'=> $last_change->created_at
                );
                $json = json_encode($result);
                echo $json;
                break;
            }else{
                sleep(1);
            }
        }

    }else{
        $msgdata = $Msg->select(array('created_at','body'))->orderBy('created_at','asc')->get();
        return View::make('brightcms.Chatting.Chatting',compact('msgdata'));
    }
}

GET Request Header

Request URL:http://localhost/msgsys/public/cms/Chatting/create?timestamp=2014-06-09+06%3A49%3A11
Request Headers CAUTION: Provisional headers are shown.
Accept:*/*
Cache-Control:no-cache
Pragma:no-cache
Referer:http://localhost/msgsys/public/cms/Chatting/create
Chrome/35.0.1916.114 Safari/537.36
X-Requested-With:XMLHttpRequest
Query String Parametersview sourceview URL encoded
timestamp:2014-06-09 06:49:11

Btw, bonus points for best ways to :

  1. Improve the code so that it makes the most out of Laravel 4's functions and ways of doing stuff.
  2. Run NodeJS on dreamhost/shared server without any restriction. Currently, there's this problem.

So yeah, how do I fix my code so that when I input a new value, the app will update a div to display the newly inputted value like a real-time app?

I'm still quite new in Laravel and would appreciate criticisms/advises. Thank you very much!


Solution

  • From what I see, I think infinite while loop is the problem here.

    PHP and Sockets

    If you cannot use NodeJS, try out PHP with Sockets. Should work for this pretty well!

    Improvements

    You said you search for improvements. Here they are.
    Plus I would use Angular to bind data retrieved from server to the view.

    The view file

    <html>
        <head>
            <title></title>
            {{ HTML::script('//cdnjs.cloudflare.com/ajax/libs/jquery/2.1.1/jquery.min.js') }}
            <style>
                #chat {
                    width: 300px;
                }
                #input {
                    border: 1px solid #ccc;
                    width: 100%;
                    height: 30px;
                }
                #messages {
                    padding-top: 5px;
                }
                #messages > div {
                    background: #eee;
                    padding: 10px;
                    margin-bottom: 5px;
                    border-radius: 4px;
                }
            </style>
        </head>
        <body>
            <div id="chat">
                <input id="input" type="text" name="message" value="">
                <div id="messages">
                </div>
            </div>
    
            <script>
                var $messagesWrapper = $('#messages');
    
                // Append message to the wrapper,
                // which holds the conversation.
                var appendMessage = function(data) {
                    var message = document.createElement('div');
                    message.innerHTML = data.body;
                    message.dataset.created_at = data.created_at;
                    $messagesWrapper.append(message);
                };
    
                // Load messages from the server.
                // After request is completed, queue
                // another call
                var updateMessages = function() {
                    var lastMessage = $messagesWrapper.find('> div:last-child')[0];
                    $.ajax({
                        type: "POST",
                        url: '{{ url('chat/refresh') }}',
                        data: {
                            from: ! lastMessage ? '' : lastMessage.dataset.created_at
                        },
                        success: function(messages) {
                            $.each(messages, function() {
                                appendMessage(this);
                            });
                        },
                        error: function() {
                            console.log('Ooops, something happened!');
                        },
                        complete: function() {
                            window.setTimeout(updateMessages, 2000);
                        },
                        dataType: 'json'
                    });
                };
    
                // Send message to server.
                // Server returns this message and message
                // is appended to the conversation.
                var sendMessage = function(input) {
                    if (input.value.trim() === '') { return; }
    
                    input.disabled = true;
                    $.ajax({
                        type: "POST",
                        url: '{{ url('/chat') }}',
                        data: { message: input.value },
                        success: function(message) {
                            appendMessage(message);
                        },
                        error: function() {
                            alert('Ooops, something happened!');
                        },
                        complete: function() {
                            input.value = '';
                            input.disabled = false;
                        },
                        dataType: 'json'
                    });
                };
    
                // Send message to the servet on enter
                $('#input').on('keypress', function(e) {
                    // Enter is pressed
                    if (e.charCode === 13) {
                        e.preventDefault();
                        sendMessage(this);
                    }
                });
    
                // Start loop which get messages from server.
                updateMessages();
            </script>
        </body>
    </html>
    

    Routes

    Route::post('chat/refresh', function() {
        $from = Input::get('from', null);
    
        if (is_null($from)) {
            $messages = Message::take(10);
        } else {
            $messages = Message::where('created_at', '>', $from);
        }
    
        return $messages->latest()->get();
    });
    
    Route::post('chat', function() {
        return Message::create(['body' => Input::get('message')]);
    });
    
    Route::get('chat', function() {
        return View::make('chat');
    });
    

    Model

    class Message extends Eloquent
    {
    
        protected $fillable = ['body'];
    }
    

    I think, code is pretty straighforward... Comments should explain everything.