Chat tutorial using beta3 (epic)

I thought i’d share some of our process with you guys by making a tutorial on how to build a very native feeling chat that works well on both Android and iOS.

DISCLAIMER: This is a rather early prototype and cleaning up our code is still “to-do” in this sprint


Demo Video

Here is a video of the end result, from an iOS device: https://vidd.me/v/DK8


What’s involved?

  • The ionic fork of cordova’s Keyboard plugin
  • My custom Input directive
  • $ionicScrollDelegate
  • and some tactful CSS and Javascript

The Keyboard

In my opinion, drifty have done better work with this plugin than the open source community have (that, or apache don’t listen to PR’s)

You can get the plugin here: https://github.com/driftyco/ionic-plugins-keyboard

Install it with cordova plugin add https://github.com/driftyco/ionic-plugins-keyboard.git

Add the following to your config.xml

<feature name="Keyboard">
    <param name="ios-package" value="IonicKeyboard" onload="true" />
</feature>

Put this in your app’s startup file. It will hide the previous/next/done bar that appears above your keyboard. Drifty said the plugin is still in a work-in-progress, but with our testing we haven’t had a single issue with it.

app.run(function($ionicPlatform) {
    $ionicPlatform.ready(function() {    
        if(window.cordova){
            cordova.plugins && cordova.plugins.Keyboard && cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
        }        
    });
});

Input Directive

In order to get some extra functionality from the keyboard and inputs, we made the following directive. We use it throughout our app so it gets added to all inputs. If you only wanted it for messaging you could change the directive to be a class you add to inputs, instead of every single input.

app.directive('input', function($timeout){
    return {
        restrict: 'E',
        scope: {
            'returnClose': '=',
            'onReturn': '&',
            'onFocus': '&',
            'onBlur': '&'
        },
        link: function(scope, element, attr){
            element.bind('focus', function(e){
                if(scope.onFocus){
                    $timeout(function(){
                        scope.onFocus();
                    });
                }        
            });
            element.bind('blur', function(e){
                if(scope.onBlur){
                    $timeout(function(){
                        scope.onBlur();
                    });
                }
            });
            element.bind('keydown', function(e){
                if(e.which == 13){
                    if(scope.returnClose) element[0].blur();
                    if(scope.onReturn){
                        $timeout(function(){
                            scope.onReturn();
                        });                        
                    }
                } 
            });   
        }
    }
});

The Messaging HTML and CSS

The html for the view is extremely simple, but we put a lot of work into finding something that allowed us to attach the input to the top of the keyboard. Note that we put the input outside of the ion-content. This gives us far greater control, but we also lose some of ionic’s handling (which targets inputs inside of the ion-content) so we need to handle this manually. We use jade templating which is built using gulp (if you are new to jade, it shouldnt be hard to see how it translates to html)

ion-view.messaging-view(title='Messages')
    ion-content(ng-style='{ bottom: data.keyboardHeight + "px" }')
        .message(ng-repeat='message in messages' ng-class='{ other: message.userId != myId }')
            .photo(ng-style='{ backgroundImage: "url(" + message.photo + ")" }')
            .message: span {{ message.text }}

    .message-input(ng-style='{ bottom: data.keyboardHeight + "px" }')
        input(type='text' placeholder='Type your message' on-return='sendMessage()' ng-model='data.message' on-focus='inputUp()' on-blur='inputDown()')

and here is the Sass (let me know if you want the css translation):

.messaging-view {
    .scroll { min-height:100%; background:#EEE; padding:10px 0 50px; }
    .message { padding:0 10px 10px; 
        .photo { height:44px; width:44px; background-color:#D6D6D6; background-size:cover; float:left; @include border-radius(50%); }
        .message { overflow:hidden; padding:5px 5px 0; text-align:left;
            span { background:#FFF; padding:10px; display:inline-block; @include border-radiuses(0, 3px, 3px, 3px); text-align:left; }
        }
        &.other {
            .photo { float:right; }
            .message { text-align:right; 
                span { @include border-radiuses(3px, 0, 3px, 3px); }
            }
        }
    }
    .message-input { position:fixed; bottom:0; left:0; right:0; height:50px; background:#FFF; z-index:20;
        input { width:100%; height:100%; background:none; @include border-box; padding:0 15px; }
    }
}

The Controller

Almost done. We need to make our controller. For demo’s sake we aren’t using websockets or anything fancy, we just alternate between who is typing the message.

app.controller('Messages', function($scope, $timeout, $ionicScrollDelegate){

    var alternate,
        isIOS = ionic.Platform.isWebView() && ionic.Platform.isIOS();

    $scope.sendMessage = function(){
        alternate = !alternate;
        $scope.messages.push({
            userId: alternate ? '12345' : '54321',
            photo: alternate ? 'http://graph.facebook.com/1218369214/picture?width=120' : 'http://graph.facebook.com/556575560/picture?width=120',
            text: $scope.data.message
        });
        delete $scope.data.message;
        $ionicScrollDelegate.scrollBottom(true);
    }

    $scope.inputUp = function(){
        if(isIOS) $scope.data.keyboardHeight = 216;
        $timeout(function(){
            $ionicScrollDelegate.scrollBottom(true);
        }, 300);
        
    }
    $scope.inputDown = function(){
        if(isIOS) $scope.data.keyboardHeight = 0;
        $ionicScrollDelegate.resize();
    }

    $scope.data = {};
    $scope.myId = '12345';
    $scope.messages = [];

});

We assume keyboard height is 216 on iOS. We don’t need to worry about android, as the keyboard automatically sticks to the top of the keyboard.


Whats Next?

We only just built this functionality so we will need to test on more devices, most likely making a few adjustments. The controller code is still pretty dirty and a lot of it might be moved into a custom directive.

All in all, i was impressed by how “Native” we could make this. It’s almost imperceptible to the developer, let alone regular users.

Let me know what you think :smile:

23 Likes

Thanks so much for sharing, @AshConnell. Looks and feels fantastic! #webftw

Thank you very very much for sharing!

yes yes we want the css translation (as well as the html:)

Thanks a lot for @AshConnell

Can someone please post this on github?

@AshConnell Can you provide HTML ? PLzzzzzzz

This is just awesome @AshConnell, thanks for sharing !

One awesome addition would be to make it only scroll bottom if the user is at the bottom when the message is sent.

For example, if I scroll up to read old messages I don’t want to be scrolled down when I get a new one.

This would be easy to do:

if ($ionicScrollDelegate.getScrollPosition().top === $ionicScrollDelegate.getScrollView().getScrollMax().top) {
  $ionicScrollDelegate.scrollBottom(true);
}

When you’re checking the scroll position in sendMessage, this is before the new message is pushed, so the DOM will still have only the old messages in it. That means this check will work. The scrollBottom internally fires after a setTimeout, so the new message will be in the DOM and a scrollBottom will be needed by the time it actually fires.

1 Like

Also, you can have a directive listen for the native.keyboardshow event on the document, and that will give you the keyboardHeight in the event data. Then you won’t have to guess.

Hi, is it possible to send us HTML and CSS translation?

Thank you for your work!
@AshConnell

Is there an option to always display the keyboard? I am developing a chat app, and it would be nice to see the keyboard not hide upon submit…

Awesome example. Anything on github by chance?

I got the basic example provided by @AshConnell and have a repo for it.

1 Like

Thanks. I added it my repo https://github.com/HansUXdev/IonicFramework-Templates

1 Like

Hi @mhartington, in Ionic-Chat, for some reason on android, when tapping the message input field, the keyboard coming up is on top of the input field and hides what im writing (using 4.4.2), i have to click away in the app to make it go down…

any thoughts as to why that could happen?

Android has some quirks about it that we’ve tried to document.

http://ionicframework.com/docs/api/page/keyboard/

+1 @AshConnell
Thank you very much for sharing this. I was struggling with view scrolling while typing! :slight_smile:

Could you provide some code for such a directive?

Yeah guys this is quite outdated now. I might rewrite it again in the future but the technique is similar. We no longer need to get the keyboard height. We can use the keyboard-attach directive or just position it with the ion-scroll (because it shrinks when the keyboard appears)

You can wrap the input in a form instead of using the input directive also.