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