Nikola Brežnjak blog - Tackling software development with a dose of humor
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Home
Daily Thoughts
Ionic
Stack Overflow
Books
About me
  • Home
  • Daily Thoughts
  • Ionic
  • Stack Overflow
  • Books
  • About me
Nikola Brežnjak blog - Tackling software development with a dose of humor
Ionic

4th Ionic framework meetup in Čakovec

Our 4th Ionic Framework Meetup was held last Thursday, 13th of October. It was titled Designing with Macaw, and it was all about design this time.

I would like to thank Goran Levačić, the leader of incubation and education in TICM who was the presenter this time and who did a great job presenting the material and showing us some practical things in InVision and Macaw.

It will be interesting to see what the future brings since Macaw was acquired by InVision just recently and they announced that they would combine these two tools into one product till the end of 2016. This way both designers and developers will have a common platform through which they will be able to work more efficiently together.

Few pics from the meetup:

In case you’re interested in next events, be sure to check out the meetup page and join the discussion there.

Ionic

Cordova plugin for VoIP push notifications

TL;DR

This plugin didn’t exist, so I made it – Cordova plugin for receiving VoIP push notifications on iOS 8.0+ only.

Installation

For Ionic:

ionic plugin add cordova-ios-voip-push

For Cordova:

cordova plugin add cordova-ios-voip-push

Usage

var push = VoIPPushNotification.init();

push.on('registration', function(data) {
    log("[Ionic] registration callback called");
    log(data);

    //data.deviceToken;
    //do something with the device token (probably save it to your backend service)
});

push.on('notification', function(data) {
    log("[Ionic] notification callback called");
    log(data);

    // do something based on received data
});

push.on('error', function(e) {
    log(e);
});

Please see the Ionic demo for the exact usage example.

Running the demo

Ionic setup

Clone this repo:

git clone https://github.com/Hitman666/cordova-ios-voip-push.git

CD into the cloned project and the Ionic demo project:

cd cordova-ios-voip-push && cd ionicDemo

Install the dependencies:

npm install && bower install

Install the platform and plugins (please note that this process may take a while to complete):

ionic state reset

Add the plugin (either one of three options would work):

ionic plugin add cordova-ios-voip-push

or

ionic plugin add ../thePlugin/VoIPPushNotification

or like this:

ionic plugin add https://github.com/Hitman666/cordova-ios-voip-push.git

Prepare the project:

ionic prepare ios

Open the project in XCode by going into platforms/ios and opening up the pluginTest.xcodeproj file.

XCode setup

If you don’t have an AppID and VoIP push certificate created in your Apple developer account, you can do so by following my instructions from the How to create a native iOS app that can receive VoIP push notifications tutorial.

Take your time to do that an then come back, I’ll wait.

To use the VoIP push in the app, you need to turn ON the Background Modes for your app and check few of the checkboxes:

Make sure you select the following options:

  • Audio, Airplay, and Picture in Picture
  • Voice over IP
  • Background fetch
  • Remote notifications

Next, you need to add PushKit framework to your project:

Also (yeah, I know, a lot of setup), you need to make sure that you set the appropriate Bundle Identifier. You should have read about this in the tutorial I linked above.

After you run the project, in the XCode console you should see

and on your device:

You can send VoIP pushes to yourself by editing the simplepush.php file (in the pushSendingScript folder from the Github repo). Just make sure you add your own device id that you’ll see show up on your phone. You have more options to send VoIP push, two of which you can read in the post linked few times above.

!TL;DR

Disclaimer:
This tutorial was inspired by the official phonegap-plugin-push plugin, and the fact that this kind of VoIP support didn’t exist. There were requests for it here, here and here. As well as on freelance sites like here, here – hehe, hope these guys will send some donations to my favorite charity if they end up using this now 😉

Also, I don’t ‘do’ ObjectiveC for a living (but I may change my mind after completing this :)), so I would really appreciate the constructive feedback in making this plugin better, so I look forward to your comments and potential pull requests!

In my previous tutorial I showed how to create a native iOS app with Swift (ObjectiveC code also available) that can receive VoIP push notifications sent with Houston, custom PHP script or through Amazon SNS.

In this tutorial I’m going to present to you the Cordova plugin that does the same thing; it allows hybrid iOS applications to receive the VoIP push notifications.

Building Cordova plugins

I won’t go into the details of Cordova plugin building in this document. If you’re new to building Cordova plugins, you can check out this tutorial and official docs, as they were indispensable in my quest to learn as much as possible about it.

plugin.xml

<?xml version='1.0' encoding='utf-8'?>
<plugin id="com.nikola-breznjak.voippush" version="1.0.0" xmlns="http://apache.org/cordova/ns/plugins/1.0" xmlns:android="http://schemas.android.com/apk/res/android">
    <name>VoIPPushNotification</name>
    <js-module name="VoIPPushNotification" src="www/VoIPPushNotification.js">
        <clobbers target="VoIPPushNotification" />
    </js-module>

    <platform name="ios">
        <config-file target="config.xml" parent="/*">
            <feature name="VoIPPushNotification">
                <param name="ios-package" value="VoIPPushNotification" />
            </feature>
        </config-file>

        <header-file src="src/ios/VoIPPushNotification.h" />
        <source-file src="src/ios/VoIPPushNotification.m" />
    </platform>
</plugin>

In this file you basically define:

  • for which platform is this plugin (<platform name="ios">)
  • where the source files of your plugin will be (header-file and source-file elements)
  • where is the JavaScript file that will be the bridge from Cordova to native code (js-module tag src property)
  • what will be the plugins name by which you’ll reference it in the Cordova/Ionic code (<clobbers target="VoIPPushNotification" />)

www/VoIPPushNotification.js

This file, stripped of it’s comments is very short (75 LOC):

var exec = cordova.require('cordova/exec');

var VoIPPushNotification = function() {
    this._handlers = {
        'registration': [],
        'notification': [],
        'error': []
    };

    // triggered on registration and notification
    var that = this;
    var success = function(result) {
        if (result && result.registration === 'true') {
            that.emit('registration', result);
        }
        else if (result) {
            that.emit('notification', result);
        }
    };

    // triggered on error
    var fail = function(msg) {
        var e = (typeof msg === 'string') ? new Error(msg) : msg;
        that.emit('error', e);
    };

    // wait at least one process tick to allow event subscriptions
    setTimeout(function() {
        exec(success, fail, 'VoIPPushNotification', 'init');
    }, 10);
};

VoIPPushNotification.prototype.on = function(eventName, callback) {
    if (this._handlers.hasOwnProperty(eventName)) {
        this._handlers[eventName].push(callback);
    }
};

VoIPPushNotification.prototype.off = function (eventName, handle) {
    if (this._handlers.hasOwnProperty(eventName)) {
        var handleIndex = this._handlers[eventName].indexOf(handle);
        if (handleIndex >= 0) {
            this._handlers[eventName].splice(handleIndex, 1);
        }
    }
};

VoIPPushNotification.prototype.emit = function() {
    var args = Array.prototype.slice.call(arguments);
    var eventName = args.shift();

    if (!this._handlers.hasOwnProperty(eventName)) {
        return false;
    }

    for (var i = 0, length = this._handlers[eventName].length; i < length; i++) {
        var callback = this._handlers[eventName][i];
        if (typeof callback === 'function') {
            callback.apply(undefined,args);
        } else {
            console.log('event handler: ' + eventName + ' must be a function');
        }
    }

    return true;
};


module.exports = {
    init: function(options) {
        return new VoIPPushNotification(options);
    },

    VoIPPushNotification: VoIPPushNotification
};

This code follows the structure of the phonegap-plugin-push push.js.

Once you call the init method, you get the new VoIPPushNotification object which then exposes the methods for listening to the registration, notification and error events.

src/ios/VoIPPushNotification.h

#import <Cordova/CDV.h>
#import <PushKit/PushKit.h>

@interface VoIPPushNotification : CDVPlugin <PKPushRegistryDelegate>

@property (nonatomic, copy) NSString *VoIPPushCallbackId;
- (void)init:(CDVInvokedUrlCommand*)command;

@end

Since we’re writing in ObjectiveC, we need to define the functions in the .h file. Here we do few things:

  • import Cordova and PushKit
  • add an instance property VoIPPushCallbackId
  • add an init function declaration

src/ios/VoIPPushNotification.m

#import "VoIPPushNotification.h"
#import <Cordova/CDV.h>

@implementation VoIPPushNotification

@synthesize VoIPPushCallbackId;

- (void)init:(CDVInvokedUrlCommand*)command
{
  self.VoIPPushCallbackId = command.callbackId;
  NSLog(@"[objC] callbackId: %@", self.VoIPPushCallbackId);

  //http://stackoverflow.com/questions/27245808/implement-pushkit-and-test-in-development-behavior/28562124#28562124
  PKPushRegistry *pushRegistry = [[PKPushRegistry alloc] initWithQueue:dispatch_get_main_queue()];
  pushRegistry.delegate = self;
  pushRegistry.desiredPushTypes = [NSSet setWithObject:PKPushTypeVoIP];
}

- (void)pushRegistry:(PKPushRegistry *)registry didUpdatePushCredentials:(PKPushCredentials *)credentials forType:(NSString *)type{
    if([credentials.token length] == 0) {
        NSLog(@"[objC] No device token!");
        return;
    }

    //http://stackoverflow.com/a/9372848/534755
    NSLog(@"[objC] Device token: %@", credentials.token);
    const unsigned *tokenBytes = [credentials.token bytes];
    NSString *sToken = [NSString stringWithFormat:@"%08x%08x%08x%08x%08x%08x%08x%08x",
                         ntohl(tokenBytes[0]), ntohl(tokenBytes[1]), ntohl(tokenBytes[2]),
                         ntohl(tokenBytes[3]), ntohl(tokenBytes[4]), ntohl(tokenBytes[5]),
                         ntohl(tokenBytes[6]), ntohl(tokenBytes[7])];

    NSMutableDictionary* results = [NSMutableDictionary dictionaryWithCapacity:2];
    [results setObject:sToken forKey:@"deviceToken"];
    [results setObject:@"true" forKey:@"registration"];

    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
    [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]]; //[pluginResult setKeepCallbackAsBool:YES];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
}

- (void)pushRegistry:(PKPushRegistry *)registry didReceiveIncomingPushWithPayload:(PKPushPayload *)payload forType:(NSString *)type
{
    NSDictionary *payloadDict = payload.dictionaryPayload[@"aps"];
    NSLog(@"[objC] didReceiveIncomingPushWithPayload: %@", payloadDict);

    NSString *message = payloadDict[@"alert"];
    NSLog(@"[objC] received VoIP msg: %@", message);

    NSMutableDictionary* results = [NSMutableDictionary dictionaryWithCapacity:2];
    [results setObject:message forKey:@"function"];
    [results setObject:@"someOtherDataForField" forKey:@"someOtherField"];

    CDVPluginResult* pluginResult = [CDVPluginResult resultWithStatus:CDVCommandStatus_OK messageAsDictionary:results];
    [pluginResult setKeepCallback:[NSNumber numberWithBool:YES]];
    [self.commandDelegate sendPluginResult:pluginResult callbackId:self.VoIPPushCallbackId];
}

@end

In the implementation file we define our functions. The first one is init, and here we basically register for VoIP push notifications by using PKPushRegistry.

Then, to satisfy that, we implement two functions: didUpdatePushCredentials and didReceiveIncomingPushWithPayload.

The first one is triggered with the device token, which we then send back to the ‘JS world’. We listen for this in our Ionic/Cordova apps and send this information to our backend so that later when we’re going to send the push we know to which device id.

The second one is triggered when we receive the actual VoIP push notification. In this example, we take out the message (@alert) and return it to the ‘JS world’. In my particular case, this can be a message with a certain keyword, so that then I know what to do in my app based on that keyword.

Again, I have to stress here that you may wanna alter this to your liking and process even more data which you receive through the VoIP push notification.

Conclusion

I hope this plugin will come handy to you. Also, I hope that this will give you enough information so that you’ll be dangerous enough to go and fiddle with the code yourself.

As I’ve mentioned before, I would really appreciate the constructive feedback so that we can make this plugin better, so I look forward to your comments and optional pull requests.

Btw, I updated these posts with this solution:

  • StackOverflow post
  • Ionic framework forum
  • phonegap-plugin-push plugin

and I have also asked the main maintainer of phonegap-plugin-push plugin if this would make sense to put as a PR on that plugin.

I will update you on the progress of all this, and till then – code on!

#Cordova plugin for #VoIP push notifications https://t.co/jkn56YWGTU

— Nikola Brežnjak (@HitmanHR) September 30, 2016

iOS

How to create a native iOS app that can receive VoIP push notifications

TL;DR

In this tutorial, I’ll give you step by step instructions on how to create a native iOS app with Swift (ObjectiveC code also available in the Github repo) that can receive VoIP push notifications sent with Houston, custom PHP script or through Amazon SNS.

This tutorial is largely inspired by this one (one of the few that actually made sense and made the steps manageable). However, I’ve added some not so obvious steps and fixed the issues that I had with the newest versions. Also, I added the link to the finished project on Github for further reference.

VoIP notifications

The official documentation can be found here. Few of the advantages are:

  • app is automatically relaunched if it’s not running when a VoIP push is received
  • device is woken up only when VoIP push occurs (saves battery)
  • VoIP pushes go straight to your app for processing and are delivered without delay
  • app is automatically relaunched if it’s not running when a VoIP push is received

Prerequisite settings

Apple provides us with a framework called PushKit to support using this VoIP push feature. However, we need to configure some additional settings to get this working.

Creating an App ID

In case you don’t already have an app (and consequently an App ID), you need to create one.

First, login to your Apple developer account and access Certificates, Identifier & Profiles:

Next, go to Identifiers->App IDs and then click on the + button.

Two important things to fill out here are App ID Description and so-called Bundle ID (this will most likely be something like com.yourdomain.yourappname):

Although not seen in the screenshots above, I used nikolaVoipTest as Bundle ID. This will be important in the next step.

Generating a VoIP push certificate

To generate a VoIP push certificate you first need to click on the All button in the Certificates section on the left-hand side. Then, click the + button:

On the next page you need to select the VoIP Services Certificate:

and on the one after that you need to select the App ID for which you’re creating this VoIP certificate:

Next, you’ll be presented with instructions on how to create a so-called CSR (Certificate Signing Request) file:

Once you create that file, you’ll select it for upload on the next screen. If everything goes well you’ll be given the certificate which you have to download:

After you download the certificate, open it up, and this should open the Keychain Access application, and you should see the certificate under the My Certificates section:

Creating the app

With all the setting out of our way, we can now start Xcode and create a new Single view application project:

Take special care when setting the Product Name as the Bundle Identifier is set automatically from it. We need to set this to be the same as the Bundle identifier that we’ve set in the steps above.

Setting the appropriate capabilities

To use the VoIP push in the app, we need to turn ON the Background Modes for our app and check few of the checkboxes:

Make sure you select the following options:

  • Audio, Airplay, and Picture in Picture
  • Voice over IP
  • Background fetch
  • Remote notifications

Adding the code

Open AppDelegate.swift and at the top add the import PushKit statement.

Then, in the didFinishLaunchingWithOptions part of the application function make sure you register for notifications like this:

func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
        //Enable all notification type. VoIP Notifications don't present a UI but we will use this to show local nofications later
        let notificationSettings = UIUserNotificationSettings(forTypes: [.Badge, .Sound, .Alert], categories: nil)

        //register the notification settings
        application.registerUserNotificationSettings(notificationSettings)

        //output what state the app is in. This will be used to see when the app is started in the background
        NSLog("app launched with state \(application.applicationState)")

        return true
}

Since we are using the registerUserNotificationSettings method we need to implement it’s delegate callback application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings):

func application(application: UIApplication, didRegisterUserNotificationSettings notificationSettings: UIUserNotificationSettings) {

    //register for voip notifications
    let voipRegistry = PKPushRegistry(queue: dispatch_get_main_queue())
    voipRegistry.desiredPushTypes = Set([PKPushTypeVoIP])
    voipRegistry.delegate = self;
}

In this callback, we register the VoIP notifications since we know that the user has agreed to receive notifications (since this function has been called). We enabled VoIP notifications by declaring the voipRegistry object.

At this point, you will get an error on the voipRegistry.delegate = self; line saying Cannot assign a value of type 'AppDelegate' to type 'PKPushRegistryDelegate!'.

The delegate for voipRegistry is of type PKPushRegistryDelegate which has three methods, two of which are required (didUpdatePushCredentials and didReceiveIncomingPushWithPayload). We have to define a so-called extension of the AppDelegate class. We do that by adding the following code after all the current code in the AppDelegate.swift file:

extension AppDelegate: PKPushRegistryDelegate {

    func pushRegistry(registry: PKPushRegistry!, didUpdatePushCredentials credentials: PKPushCredentials!, forType type: String!) {

        //print out the VoIP token. We will use this to test the notification.
        NSLog("voip token: \(credentials.token)")
    }

    func pushRegistry(registry: PKPushRegistry!, didReceiveIncomingPushWithPayload payload: PKPushPayload!, forType type: String!) {

        let payloadDict = payload.dictionaryPayload["aps"] as? Dictionary<String, String>
        let message = payloadDict?["alert"]

        //present a local notifcation to visually see when we are recieving a VoIP Notification
        if UIApplication.sharedApplication().applicationState == UIApplicationState.Background {

            let localNotification = UILocalNotification();
            localNotification.alertBody = message
            localNotification.applicationIconBadgeNumber = 1;
            localNotification.soundName = UILocalNotificationDefaultSoundName;

            UIApplication.sharedApplication().presentLocalNotificationNow(localNotification);
        }

        else {

            dispatch_async(dispatch_get_main_queue(), { () -> Void in

                let alert = UIAlertView(title: "VoIP Notification", message: message, delegate: nil, cancelButtonTitle: "Ok");
                alert.show()
            })
        }

        NSLog("incoming voip notfication: \(payload.dictionaryPayload)")
    }

    func pushRegistry(registry: PKPushRegistry!, didInvalidatePushTokenForType type: String!) {

        NSLog("token invalidated")
    }
}

After adding this extension you will note that the previously mentioned error disappears.

In the first function, we merely output the device token. We will need this token in the next section when we’ll be testing our app with sending VoIP push notifications.

In the second one we ‘act’ on the received VoIP push notification. In this concrete case, we show a local notification if the app is in the background or an alert if we’re in the app. The third function (didInvalidatePushTokenForType) is used for handling when the token is invalidated.

Testing VoIP notifications

We have several options for testing our app. I’ll cover few of them in this section:

  • CLI program called Houston
  • PHP script called SimplePush
  • Amazon SNS

Getting the device token

Run your app in Xcode and make sure you run it on an actual device. The console output should look something like this:

2016-09-19 10:08:53.793
voipTestNikola[1876:2020432]
app launched with state UIApplicationState

2016-09-19 10:08:53.877
voipTestNikola[1876:2020432]
voip token: <40cc4209 d0f3ac25 95a7e937 3282897b 211231ef ba66764c 6fd2befa b42076cb>

Take note of the voip token from the output above (of course, the one from your Xcode debug window and not the one I’ve pasted above ;)), as we’ll need it in the next sections.

Preparing the certificate file

The VoIP certificate file that we’ve downloaded and added to the KeyChain has to be converted to a different file format so that we’ll be able to use it with the tools and services that I’ve listed above.

First, you need to open the KeyChain app on your Mac and then Export (Right click then select Export) the certificate:

You will get a .p12 file.

Now, navigate to the folder where you exported this file and execute the following command:

openssl pkcs12 -in YOUR_CERT.p12 -out VOIP.pem -nodes -clcerts

This will generate VOIP.pem file which we’ll use in the next sections.

Houston

Even though the docs say you can install it simply with gem install houston, you will most likely end up (after some StackOverflow searching) using this command to install it:

sudo gem install -n /usr/local/bin houston

This way you’ll install it to your local bin directory to which you have full rights.

Houston installed one more tool that will help us send the notifications like this:

With Terminal navigate to the folder where you have your certificate, copy the device id from above and execute a command like this:

apn push "<40cc4209 d0f3ac25 95a7e937 3282897b 211231ef ba66764c 6fd2befa b42076cb>" -c VOIP.pem -m "Testing VoIP notifications!"

Please note to change the VOIP.pem to whatever you named the file in the steps above.

You should get the following output in your terminal:

And, you should see this on your phone in case it was in the foreground:

In case the app was in the background you should see:

Also, I made a short video showcasing how the app behaves when it’s closed and receives a VoIP push (it’s woken up and run).

SimplePush

SimplePush (simplepush.php in the project root directory) is a PHP script that can be found on the net in few various forms and from few various authors. However, the code is less then 50 LOC, so it’s quite simple to understand. We need to set just a few of the config options:

// Put your device token here (without spaces):
$deviceToken = 'deviceToken';

// Put your private key's passphrase here:
$passphrase = 'passphrase';

// Put your alert message here:
$message = 'message';

// Put the full path to your .pem file
$pemFile = 'pemFile.pem';

and then run the script with php simplepush.php.

The only thing to note here is that we need to insert the device token without spaces and without < and > characters.

Amazon SNS

Amazon has really good documentation for preparing everything you need, and you can take a look at it here.

After you have all the prerequisites, you should follow these instructions to create the so-called ‘platform application’. Settings that I’ve used are on the image below:

After this you can add a platform endpoint by pasting your device id (again, as in the PHP example; without spaces and < and > characters).

Finally, you have to select this endpoint and click on the Publish to endpoint button:

Here you can enter some test data like shown below:

After you click on the Publish message you should get a notification on your phone.

Conclusion

In this tutorial you’ve learned how to create a native iOS app with Swift that can receive VoIP push notifications sent with Houston, custom PHP script or through Amazon SNS.

I bolded native for a reason in the sentence above. As you know, I’m a big fan of hybrid apps and Ionic framework in particular.

This post is a part of the recent task that I had to do to create the Cordova VoIP push plugin. To successfully complete that, I first made the native app and then the plugin, since (unfortunately) the plugin wasn’t available.

You can read all about how I created the Cordova plugin for receiving VoIP push notifications in this post.

How to create a #native #iOS app that can receive #VoIP push notifications https://t.co/PFGq7rqXZr

— Nikola Brežnjak (@HitmanHR) September 30, 2016

Angular 2

How to use http in Angular 2 via angular-cli?

I initially tested and wrote a tutorial for Pluralsight by using the angular-cli version 1.0.0-beta.9.

However, I’ve tried today with the latest version and for the life of me I couldn’t figure out how to use HTTP to call some service.

In the end, I figured what the issue was, so I’m putting it out here so it may be useful to someone else as well.

So, when I installed angular-cli with npm install -g angular-cli I haven’t at first noticed this error:
UNMET PEER DEPENDENCY [email protected]

What I ended up doing now was

npm install webpack -g && npm install webpack --save-dev

and now I can normally include Http into my component like this:

import { Http, Response } from '@angular/http';

and then use it within a class like this:

Hope this helps someone.

Ionic

3rd Ionic framework meetup in Čakovec

Yesterday I held the third Ionic framework meetup in Čakovec titled Getting started with Angular 2.

I showed how to get started with Angular 2 by using the angular-cli project to start, scaffold and test Angular 2 applications. You can read the tutorial that this talk was based on here: Getting started with Angular 2 by building a Giphy search application.

Two pics from the meetup:

thumb_img_7241_1024

thumb_img_7243_1024

I would like to thank Goran Levačić, the leader of incubation and education in TICM, for securing us the place for this meetup and company Axiom for the provided drinks.

In case you’re interested in next events, be sure to check out the meetup page and join the discussion there.

CodeProject, NodeJS

Raneto Google OAuth login

TL;DR

Raneto allows only basic username/password authentication, so I added Google OAuth support. This option can be turned on by setting the googleoauth option in the config.default.js file to true, and by supplying the OAuth config object as outlined in the guides below. Additionally, you can allow only emails from the certain domain to use the service with one config setting.

The basic idea was taken from the Google Cloud Platform Node.js guide.

This has been submitted as a pull request on the official Raneto Github repository. This is my way of saying thanks to an awesome author of Raneto. edit: 13.09.2016: The pull request was approved and merged!.

Steps on how to reproduce this on fresh copy

Below are the steps one needs to take to get this working on a fresh copy of Raneto. In case this won’t make it to the official repo, you can clone my fork here. Just make sure you set your Google OAuth credentials properly (more about this in the X section).

Install packages via npm

Make sure you first install Raneto dependencies after you clone it.

Install the following packages:

  • npm install passport --save-dev
  • npm install passport-google-oauth20 --save-dev

Editing the app/index.js file

  • Add passport: var passport=require('passport'); just after raneto is required.
  • Add oauth2 middleware: var oauth2= require('./middleware/oauth2.js'); in the config block, just afer error_handler.js middleware.
  • Change secret to secret:config.secret, in the // HTTP Authentication section.
  • >>> Remove the rn-login route app.post('/rn-login', route_login);
  • >>> Remove the logout route: app.get('/logout', route_logout);
  • Add the following Oauth settings, just before the app.post('/rn-login', route_login); line:
// OAuth2
if (config.googleoauth === true) {
app.use(passport.initialize());
app.use(passport.session());
app.use(oauth2.router(config));
app.use(oauth2.template);
}
  • Change the Online Editor Routes to look like this now:
// Online Editor Routes
if (config.allow_editing === true) {
if (config.googleoauth === true) {
app.post('/rn-edit', oauth2.required, route_page_edit);
app.post('/rn-delete', oauth2.required, route_page_delete);
app.post('/rn-add-page', oauth2.required, route_page_create);
app.post('/rn-add-category', oauth2.required, route_category_create);
}
else {
app.post('/rn-edit', authenticate, route_page_edit);
app.post('/rn-delete', authenticate, route_page_delete);
app.post('/rn-add-page', authenticate, route_page_create);
app.post('/rn-add-category', authenticate, route_category_create);
}
}
  • Set the root routes to be like this:
// Router for / and /index with or without search parameter
if (config.googleoauth === true) {
app.get('/:var(index)?', oauth2.required, route_search, route_home);
app.get(/^([^.]*)/, oauth2.required, route_wildcard);
}
else {
app.get('/:var(index)?', route_search, route_home);
app.get(/^([^.]*)/, route_wildcard);
}

Editing the app/middleware/authenticate.js file

Change the res.redirect(403, '/login'); line to be:

if (config.googleoauth === true) {
res.redirect('/login');
}
else {
res.redirect(403, '/login');
}

Editing the app/routes/login_page.route.js file

Add the googleoauth variable to the return object like this:

return res.render('login', {
layout : null,
lang : config.lang,
rtl_layout : config.rtl_layout,
googleoauth : config.googleoauth
});

Add the oauth2.js file

Create a new file oauth2.js in the app/middleware folder with the following content:

// Copyright 2015-2016, Google, Inc.
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

'use strict';

var express = require('express');
var debug = require('debug')('raneto');

// [START setup]
var passport = require('passport');
var GoogleStrategy = require('passport-google-oauth20').Strategy;

function extractProfile (profile) {
var imageUrl = '';
if (profile.photos &amp;&amp; profile.photos.length) {
imageUrl = profile.photos[0].value;
}
return {
id: profile.id,
displayName: profile.displayName,
image: imageUrl
};
}

// [START middleware]
// Middleware that requires the user to be logged in. If the user is not logged
// in, it will redirect the user to authorize the application and then return
// them to the original URL they requested.
function authRequired (req, res, next) {
if (!req.user) {
req.session.oauth2return = req.originalUrl;
return res.redirect('/login');
}
next();
}

// Middleware that exposes the user's profile as well as login/logout URLs to
// any templates. These are available as `profile`, `login`, and `logout`.
function addTemplateVariables (req, res, next) {
res.locals.profile = req.user;
res.locals.login = '/auth/login?return=' +
encodeURIComponent(req.originalUrl);
res.locals.logout = '/auth/logout?return=' +
encodeURIComponent(req.originalUrl);
next();
}
// [END middleware]

function router(config) {
// Configure the Google strategy for use by Passport.js.
//
// OAuth 2-based strategies require a `verify` function which receives the
// credential (`accessToken`) for accessing the Google API on the user's behalf,
// along with the user's profile. The function must invoke `cb` with a user
// object, which will be set at `req.user` in route handlers after
// authentication.
passport.use(new GoogleStrategy({
clientID: config.oauth2.client_id,
clientSecret: config.oauth2.client_secret,
callbackURL: config.oauth2.callback,
hostedDomain: config.hostedDomain || '',
accessType: 'offline',

}, function (accessToken, refreshToken, profile, cb) {
// Extract the minimal profile information we need from the profile object
// provided by Google
cb(null, extractProfile(profile));
}));

passport.serializeUser(function (user, cb) {
cb(null, user);
});
passport.deserializeUser(function (obj, cb) {
cb(null, obj);
});
// [END setup]

var router = express.Router();

// Begins the authorization flow. The user will be redirected to Google where
// they can authorize the application to have access to their basic profile
// information. Upon approval the user is redirected to `/auth/google/callback`.
// If the `return` query parameter is specified when sending a user to this URL
// then they will be redirected to that URL when the flow is finished.
// [START authorize]
router.get(
// Login url
'/auth/login',

// Save the url of the user's current page so the app can redirect back to
// it after authorization
function (req, res, next) {
if (req.query.return) {
req.session.oauth2return = req.query.return;
}
next();
},

// Start OAuth 2 flow using Passport.js
passport.authenticate('google', { scope: ['email', 'profile'] })
);
// [END authorize]

// [START callback]
router.get(
// OAuth 2 callback url. Use this url to configure your OAuth client in the
// Google Developers console
'/auth/google/callback',

// Finish OAuth 2 flow using Passport.js
passport.authenticate('google'),

// Redirect back to the original page, if any
function (req, res) {
req.session.loggedIn = true;
var redirect = req.session.oauth2return || '/';
delete req.session.oauth2return;
res.redirect(redirect);
}
);
// [END callback]

// Deletes the user's credentials and profile from the session.
// This does not revoke any active tokens.
router.get('/auth/logout', function (req, res) {
req.session.loggedIn = false;
req.logout();
res.redirect('/login');
});
return router;
}

module.exports = {
extractProfile: extractProfile,
router: router,
required: authRequired,
template: addTemplateVariables
};

This is a changed file based on the Google Node.js official example file. Notable differences are in Google strategy settings which basically load settings from our settings config:

clientID: config.oauth2.client_id,
clientSecret: config.oauth2.client_secret,
callbackURL: config.oauth2.callback,
hostedDomain: config.hostedDomain || '',

We’ll define these settings the config.default.js file now.

Editing the example/config.default.js file

Change/add the following settings:

allow_editing : true,
authentication : true,
googleoauth: true,
oauth2 : {
client_id: 'GOOGLE_CLIENT_ID',
client_secret: 'GOOGLE_CLIENT_SECRET',
callback: 'http://localhost:3000/auth/google/callback',
hostedDomain: 'google.com'
},
secret: 'someCoolSecretRightHere',

Google OAuth2 Credentials

Oauth2 settings (GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET) can be found in your Google Cloud Console-&gt;API Manager-&gt;Credentials project settings (create a project if you don’t have one yet):

The callback, if testing locally, can be set as shown above (http://localhost:3000/auth/google/callback). The hostedDomain option allows certain domains – for your use case you may want to set this to your domain.

Google+ API

If you get an error like:

Access Not Configured. Google+ API has not been used in project 701766813496 before, or it is disabled. Enable it by visiting https://console.developers.google.com/apis/api/plus/overview?project=701766813496 then retry. If you enabled this API recently, wait a few minutes for the action to propagate to our systems and retry.

Make sure you enable Google+ API for your project:

Adding Zocial CSS

To add support for the nice Zocial social buttons, download this file from their Github repo to the themes/default/public/styles/ folder.

Editing the themes/default/templates/layout.html file

Replace the login form with:

We added two scenarios for when we have Google OAuth enabled (config.googleoauth) and when we don’t (defaulting to the current Raneto behavior).

Editing the themes/default/templates/login.html file

Add zocial reference:

“

Replace the whole form-bottom classed div with the following code:

Same thing here as well. If we have Google OAuth enabled (config.googleoauth) then we show the new Google login button and hide the rest. Otherwise, we default it to the current Raneto behavior.

Testing

Congratulations, you’re done! Now, to test this locally just run the npm start from the root of your project and go to http://localhost:3000 and you should see this:

After logging in, you should see something like this:

Hope this helps someone!

#Raneto Google OAuth login step by step https://t.co/rWnoXFl0LO

— Nikola Brežnjak (@HitmanHR) September 7, 2016

Miscellaneou$

Automated Mobile Testing tools and services

Recently I did a research on the tools and services that let you test your apps (iOS, Android, Hybrid/Web) and here’s what I found.

TL;DR

In case you don’t have time to read through this and you’re looking for a quick answer on what to choose; I would go with AWS Device Farm.

It was the only service where the docs actually had clear instructions and the only service where the automated tests actually worked. If for nothing else, I would suggest using their Remote Access option, as it’s really great because it gives you the ability to test your app on the particular device with the particular OS version in real time. This is definitely useful for when a user submits a bug, and you don’t have the actual device/OS combination on which you could test.

A notable mention is Appium, which is perfect if you just want to test on your machine and your simulator. It’s open source and it even has support in AWS Device Farm.

Longer version

Below are my notes about the tools/services that I’ve tried.

AWS Device Farm

As you’ve read in the TL;DR, my choice would be AWS Device Farm, so I’ll take a bit more space here and explain why I liked it the most, and I’ll showcase the simplest usage.

The official website is here and the official documentation is here.

There are two main ways to use AWS Device Farm:

  • Automated app testing using a variety of available testing frameworks (they even have a few built-in tests called Fuzz tests which create some screenshots and a usage video as well)
    • it supports hybrid apps for both Android and iOS
    • tests run in paralel on multiple devices
    • test reports contain:
      • high-level results
      • low-level logs
      • pixel-to-pixel screenshots
      • performance data
  • Remote access to devices on which you can load, run, and interact with apps in real time
    • allows you to swipe, gesture, and interact with a device through your web browser in real time
    • details about actions that take place as you interact with the devices
    • logs and video capture of the session are produced at the end of the session for your review

Demo

The docs for setting this up are really great and straight to the point.

Once you create your first project, you have to Create a new run:

Then you have to select the Android/iOS icon and upload your .apk/.ipa file:

In the next step, you have to choose the test type that you would like to use. There are a lot of options, and as a starter (in case you don’t have your tests ready – we’ll cover these below in the Appium section) you can choose the Built-in Fuzz test.

In the next step you can select the various devices:

set some additional options and even pre-install some other apps:

After this, just review the settings and start the test. Tests will run in the background and in parallel on all the devices that you’ve set. Once this is done you will get an overview like this:

You can view the details of each run on the particular device:

You can view the screenshots, performance:

And you can even see the video of how the built-in test used your application:

Sure, these tests are not something on which we should rely on, and that’s why it’s great that we can write our own. In the next section, we’ll shortly cover Appium and why it’s so great for writing tests.

Appium

Appium is a tool that makes automation possible for iOS and Android devices. It’s architected around four key principles:

  • You shouldn’t modify your app in order to test it, as you don’t want test libraries to affect the operation of your app
  • You should be able to write your tests in any programming language, using any test runner and framework.
  • There is already a very successful automation standard, so we should reuse and extend that rather than create an entirely new model.
  • Mobile automation is for everyone. The best mobile automation tool will be open source, and not just in terms of having its code available for view. It should be governed using open source project management practices, and eagerly welcome new users and contributors.

Appium satisfies all these requirements in the following ways:

  • Appium allows you to automate your Android and iOS apps without code modification because it relies on underlying automation APIs supported by the mobile OS vendors (Apple and Google) themselves. These APIs are integrated into the development process and thus don’t require any 3rd-party libraries to be embedded in test versions of your apps.
  • Appium is built around a client/server architecture, which means Appium automation sessions are HTTP conversations (just as when you use any kind of REST API). This means clients can be written in any language. Appium has a number of clients already written, for Ruby, Java, JavaScript, Python, C#, PHP, Objective-C, and even Perl. Additionally, this means that it doesn’t matter whether an Appium server is hosted on the same machine as the tests.
  • Selenium WebDriver is without a doubt the most widely-known framework for automating web browsers from a user’s perspective. Its API is well-documented, well-understood, and is already a W3C Working Draft. Appium uses this specification and extends it cleanly with additional, mobile-specific automation behaviors.
  • Appium is open source. It should be clear from the volume of issues and pull requests that we have a very active community (who also engage with one another on our discussion forum).

Few additional learning resources:

  • Youtube 1
  • Youtube 2
  • Tuts+ Code

Demo

Once Appium is installed and running, click the proper OS icon (Droid/Apple) and select the path to your compressed .ipa file (it is required that you zip the .ipa file):

One thing I had to set here as well was the BundleID, as it didn’t work without it (BundleID can be found in your config.xml file as the id property of the widget tag).

Once this is set, you can simply click the Launch button. Once the launching is done you’ll see something like the following in the logs:

[Appium] Appium REST http interface listener started on 0.0.0.0:4723

Then you can click the Magnifier icon:

which will run your app in the simulator:

If you’re testing Hybrid apps, be sure to set the proper Context:

The best feature of Appium is that it lets you Record the tasks. To enable it, just click the Record button, navigate through the tree like structure and select the actions that you want to perform on the selected tags (Tap, Swipe, Shake, text inserting, etc.), and it generates the test code (in few different languages):

Xamarin test cloud

  • Website
  • The installation was surprisingly long
  • It turned out that once I uploaded my .ipa file it threw an error: The .ipa file does not seem to be linked with Calabash framework.
  • The main drawback is that you need to integrate the SDK into the app itself. Something which you don’t have to do on the AWS Device farm (through Appium [take a look at their four key principles])

Calabash

  • Later I figured out that Xamarin test cloud uses Calabash
  • Callabash was developed and open sourced by Xamarin and it is a set of libraries that enable you to write and execute automated acceptance tests which consist of end-user actions like Gestures, Assertions, and Screenshots
  • Calabash supports (and prefers) Cucumber

Cucumber

  • Cucumber lets you express the behavior of your app using natural language that can be understood by business experts and non-technical QA staff.

Browserstack

Browserstack doesn’t support mobile app testing currently. Here’s the official thread:

  • Can I test native apps or install apps on BrowserStack’s physical devices?

At this time, the mobile devices are available exclusively for cross-browser testing. Mobile app testing is on our roadmap for a later date.

Sauce labs

  • Website

I’ve spent some time trying to get their solution to work as they popped up on most of the searches that I’ve done. However, I can’t say this was a pleasant experience as the docs seemed to be more like ‘Hey, we know how to do this, but if you want we can offer you our consulting support :/’.

But, didn’t like their support as I’m still waiting for the answer to my question I’ve sent them. When I got the automated sales email, I followed up with the guy but again he said he couldn’t help me as he’s ‘only’ sales.

They allow you to upload your executable to their service, but only via terminal. They don’t have an interface for this on the site as AWS does, nor do they have an overview of the uploaded files.

You run all your tests from a script, and you can then see the screenshots and video of how the tests are interacting with your app:

However, the bottom line here is that I wasn’t too impressed with them.

Other services

I have also briefly looked at these services, but they didn’t seem intriguing enough to pursue any of them further.

Firebase test lab

  • Website
  • only for Android

eggPlant Mobile

  • Website
  • Only image based

Soasta

  • Website
  • Nothing special. TBH, it seemed like it’s an older version that’s not actively developed.

Squish

  • Website
  • Seems like a quite stable solution, however only locally and it needs to be implemented in the Xcode/Android project to work

Testmunk

  • Website
  • Although very nice interface and the flow is basically as on AWS, it wasn’t able to run my app (just kept popping errors). I emailed the owners but no reply to date, so…

Keynote

  • Website
  • Asked for Credit Card – lol, are we in like 2009?

iOS only testing

  • Frank
  • KeepItFunctional

Windows only apps

  • SmartBear – they have instructions on how to run it on OSX via virtualization however this still is ‘just’ an app like Appium which is open source.
  • TestingWhiz
  • SilkTest
  • Ranorex

Miscellaneous

  • Test automation trends 2016
  • StackExchange thread about automated hybrid mobile app testing

Conclusion

So, yeah, as stated in the TL;DR AWS seems like the most viable solution to me right now. In case you have experience with some other solution, or you disagree with some of the conclusions that I’ve made, please let me know in the comments.

Also, if someone has extensive experience with writing and running tests on AWS Device Farm via Appium tests, I would love to get your feedback.

#Automated #mobile #testing tools and services https://t.co/rZpnBVQ9Fh pic.twitter.com/Yzbeg2ASya

— Nikola Brežnjak (@HitmanHR) August 23, 2016

Ionic

2nd Ionic Framework Meetup Čakovec

Last week I held the second Ionic framework meetup in Čakovec.

I talked about how to get started with Test Driven Development in Ionic framework, and we had a coding session after the presentation that other participants were following. In the end, they had a clear how-to for starting testing their Ionic apps (with or without the use of TDD approach).

You can check out the presentation slides here, and you can read the tutorial that this talk was based on here: Introduction to TDD in Ionic framework.

Some pictures from the meetup are below:

I would like to thank Goran Levačić, the leader of incubation and education in TICM, for securing us the place for this meetup and company Axiom for the provided drinks.

In case you’re interested in next events, be sure to check out the meetup page and join the discussion there.

See you at the next meetup in about a month (middle of September, exact date TBD) where I’ll be talking about how to get started with Angular 2.

CodeProject, Ionic

Build an Ionic app for searching gifs using Giphy API

Last week I held the first Ionic framework meetup in Čakovec. Hereby I would like to thank Goran Levačić, the leader of incubation and education in TICM, for securing us the place for this meetup.

In case you’re interested about next events, be sure to check out the meetup page and join the discussion there.

What was it about?

First, we’ve shown how to set up the Ionic framework environment for those who didn’t yet have it and then we went through a typical application step by step.

You can see the reactions from the first meetup here, and some pictures are below. Also, TICM made a blog post about it on their site so check it out if you want (attention: only Croatian version).

Demo app

We made a simple application for searching (and showing) gifs from the Giphy website by using their API. Most apps fall into this category today; you have a service ‘somewhere’, and you call it within your app and show its data. Following the same principle, you could make an app for Youtube, IMDB, etc…

The source code for the app is on Github, and you can try it live as well.

Those of you who are already very familiar with Ionic may find the following pace too slow. If so, you can just take a look at the source code.

Starting the project

First, let’s start a new Ionic project with the following command (executed from your terminal):

ionic start giphyApp

When the command finishes, enter the new directory:

cd giphyApp

Just for testing purposes, let’s run the app to see if everything is OK:

ionic serve --lab

You should see something like this:

The --lab switch will give you a nice side by side view of how the app would look like on iOS and Android device.

Folder structure

Now, open up this folder in your editor and you should see something like this (I’m using Sublime Text 3):

When developing Ionic applications, you’ll be spending most of the time in the www folder.

Just a quick TL;DR of other folders and files that we have:

  • hooks – contains so-called Cordova hooks, which execute some code when Cordova is building your project. From my experience, I didn’t have to set this up yet
  • platforms – contains the platform specific files upon building the project
  • plugins – contains Cordova plugins which have been added (or will be) to the project
  • scss – contains SASS files
  • Bower is a front-end package manager that allows you to search, install and update your front-end libraries. You can learn more in this comprehensive tutorial. Bower saves downloaded modules defined in the .bowerrc file
  • config.xml – Cordova configuration file
  • gulpfile.js – Gulp configuration file. You can learn more in this getting started tutorial, but shortly; Gulp is a so-called JavaScript task runner which helps with tasks like minification, uglification, running unit tests, etc.
  • ionic.project – Ionic.io configuration file
  • package.json – contains the information about Node.js packages that are required in this project
  • .gitignore – defines which files are to be ignored when pushing to Github
  • README.md – projet information document written in Markdown that automatically shows as a landing page on your Github project

Let’s start writing some code

OK Batman, enough with the theory, let’s write some code!

First, let’s try to change some text on the first tab.

Sure, but, how should we know in which file is that first tab defined?

Well, as with every app, let’s start searching in the index.html

The content of that file is shown below for reference:

We see that we have a simple HTML file which in its head section has some meta tags, then we import some CSS, and finally some js files.

In the body tag we see that it has a ng-app="starter" attribute attached to it, which tells us that in some JavaScript file there is a module named starter.

If we take a look at the JavaScript files located in the js folder, we will find this starter module in the app.js file.

Ok, sure, that’s all nice, but we still don’t know which file to change!

Well, if we take a look at the templates folder (of course, Search functionality of your editor comes handy in situations like this ;)) we’ll see that the tabs-dash.html file contains the text Welcome to Ionic.

Now, remove all the code from this file except h2, and write something like Welcome to GiphySearch. Also, change the text Dashboard to GiphySearch.

Just for reference, the contents of the tab-dash.html file should now be this:

Tab text

Currently, you should have a screen that looks like this:

Those tabs don’t quite represent what we would like to have there, right?

Sure, but where would we change that?

Well, if you open up the templates/tabs.html file you’ll see where you can make such a change. So, change the title to Home.

Voila! You now have a tab named Home.

Icons

However, the icon is a bit ‘wrong’ here, don’t you think?

By looking at the HTML:

“

we can see some interesting attributes like icon-off and icon-on.

Yes, this is where you can define how our icons will look like.

Great, but, where do you find the exact class which you would put here?

Enter Ionic icons:

Search for any icon you wish by name, click on it, copy the string and place it in your icon-on and icon-off attributes.

In our case, this is what we will use:

Buttons

It’s true that we can just click the tab and move between them, but since we’re building an enterprise xD application here, let’s add a new button in the tab-dash.html file:

<a class="button button-block button-royal">Go to search</a>

In case you’re wondering where I came up with all those classes, you can view all the various buttons in their (quite good) documentation.

The ui-sref is a part of Angular’s UI Router, and it basically sets the link to which this button will take us once clicked.

At this point you should have a screen that looks like this:

and by clicking the button, you should be shown the second tab called Chats.

Some more tab modifications

OK, fine, so we click the button, and we’re shown the Chats tab. Big deal. But we don’t want the Chats tab! We’re making a Search app!

OK, easy on the coffee partner. We’ll get to this now.

So, armed with the knowledge from before we open the templates/tab-chats.html and remove everything between the ion-content tag, and we change the title to Search.

We also don’t like that icon and text on the tab, so let’s hop in the templates/tabs.html file and change the Chats tab definition to this:

What have we done? We literally changed the Chats text to Search. But, we also added different classes for the icons (again, using Ionic icons as explained before).

This is fine, but say we’re meticulous about it, and we don’t want to have the tab-chats.html, but instead we want tab-search.html. No problem, just rename the file.

Route 66

But, now we have a problem. Our button on the first tab is not working anymore. That’s because we renamed the file, so we need to set the proper references. We do that in the app.js file. Just search the file for this code:

.state('tab.chats', {
url: '/chats',
views: {
'tab-chats': {
templateUrl: 'templates/tab-chats.html',
controller: 'ChatsCtrl'
}
}
})

and change it with this:

.state('tab.search', {
url: '/search',
views: {
'tab-search': {
templateUrl: 'templates/tab-search.html',
controller: 'ChatsCtrl'
}
}
})

OK, fine Sherlock, this works now if I click on the Search tab, but it does not work if I click the button on the first tab!?

Yep, that’s right, and that’s because we have the wrong url set there. Set it to this now:

<a class="button button-block button-royal">Go to search</a>

Voila, we now have a working button on the first tab, and we have a nice, empty, ready to be awesome, Search page:

Search tab mastery

So, we now have a blank screen, and at this point, we ask ourselves:

Cool, what do we want to have here?

Well, since it’s a search app, what do you say about an input field? Great. But wait! Before you start typing those input tags, let’s first check out the Ionic docs and scroll a bit around that Forms section.

I happen to like this one, so let’s copy the following code in our tab-search.html file (inside the ion-content tag):

<div class="list list-inset"><label class="item item-input">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" />
</label></div>

OK, we’re rockin’ things by now, so let’s add some button as well (inside the div with list class):

<a class="button button-block button-royal">Search</a>

For reference, this should be the exact content of your tab-search.html now:

<div class="list list-inset"><label class="item item-input">
<i class="icon ion-search placeholder-icon"></i>
<input type="text" placeholder="Search" />
</label><a class="button button-block button-royal">Search</a>
</div>

And this is how it should look like:

Search tab action

This is all nice now, but now we would probably want something to happen when we click this button, right? What about calling some function? Great, let’s write that now!

On this button add the new attribute ng-click, which tells Angular that once this button is clicked call the function that’s written as the attribute’s value. Ok, sure, a lot of fluff here, this is how it looks like in the code:

<a class="button button-block button-royal">Search</a>

And, in plain English; once the button is clicked the function performSearch will be called.

But, again, if you click the button, nothing happens!? Well, that’s because there’s no performSearch function defined anywhere. Let’s do that now.

All the controllers are currently defined in the controllers.js file. But, how do you know which controller you have to change? Well, if you take a look at the route definitions in the app.js fille you will see remember we changed the search tab like this before:

.state('tab.search', {
url: '/search',
views: {
'tab-search': {
templateUrl: 'templates/tab-search.html',
controller: 'ChatsCtrl'
}
}
})

So, the answer would be: we need to change the ChatsCtrl controller in the controllers.js file. However, we’re meticulous, remember? So, we don’t want to have ChatsCtrl, instead, we want to have SearchCtrl. No problem, just change the line in the listing above to:

controller: SearchCtrl

Controllers

In the controllers.js file remove the ChatsCtrl controller code completely, and instead write this:

.controller('SearchCtrl', function($scope) {
console.log("Hello from the Search controller");
})

For reference, the contents of the whole controllers.js file should now be:

angular.module('starter.controllers', [])

.controller('DashCtrl', function($scope) {})

.controller('SearchCtrl', function($scope) {
console.log("Hello from the Search controller");
})

.controller('ChatDetailCtrl', function($scope, $stateParams, Chats) {
$scope.chat = Chats.get($stateParams.chatId);
})

.controller('AccountCtrl', function($scope) {
$scope.settings = {
enableFriends: true
};
});

Now when we load the app and go to the search tab, we will see the following message in the DevTools Console window (this is in the Chrome browser, but I’m sure you know how to use this in the browser you use, right?):

Adding the function

We’re outputting something to the browsers console, but we still get nothing when we click the button. To fix this add the following code to the SearchCtrl controller:

$scope.performSearch = function (){
console.log("button click");
};

When you click the button, your browser console should look something like this:

Oh man, you said this would be slow paced, but this is like watching a video at 0.25x speed. Yeah, I love you too 🙂

What would we like to do now when we click the button? Well, it would be logical at this point that we would ‘somehow’ output to the console what the user entered in the input box. Here’s the easiest way to do this:

Enter this code in the SearchCtrl controller:

$scope.search = {}
$scope.search.term = 'cats';

and now, in the tab-search.html file, alter the input to this:

<input type="text" placeholder="Search" />

Note that we added

ng-model="search.term"

which basically binds the Angular model to this input. In case you’re wondering why we haven’t just used the ng-model="search" then you’re in for a treat with this answer.

To output the search term to your browser console just adjust the function like this:

$scope.performSearch = function (){
console.log("search term: " + $scope.search.term);
};

When you load your app and click the Search button (without changing the input value), you should get this:

Note how the cats text has been automatically populated as the app loaded. That’s some powerful two-way binding in Angular.

Giphy API

Finally, we come to the cool part, and that is to fetch some data from the service and to show it in our app (in our case, we’ll show images).

So, how do we get this API? Well, if you do a simple google search for giphy api and open the first link you’ll get the documentation for their API.

So, what do we need? Well, we need the search API. If you scroll a bit, you’ll find the following link:

http://api.giphy.com/v1/gifs/search?q=funny+cat&amp;api_key=dc6zaTOxFJmzC

Great, now we see what kind of a request we need to create to search Giphy’s gif database for a certain term.

If you open this link in the browser, you’ll see what the service returns. Something like:

Ok, and now what? So, now we want to fetch this data from within our app. Ok, but how do we do that?

Angular HTTP requests

Angular has an $http service for sending HTTP requests to some service API endpoint.

Let’s jump a bit and change our performSearch function to this:

$scope.performSearch = function (){
var searchTerm = $scope.search.term.replace(/ /g, '+');

console.log("search term: " + searchTerm);

var link = 'http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=' + searchTerm;

$http.get(link).then(function(result){
console.log(result);
});
};

Line by line explanation of the new code:

  • added a new searchTerm variable, and used the replace function on the $scope.search.term variable, with the regex for global matching (the g switch) that basically replaces all the spaces with +. We did this in case someone enters ‘cats and dogs’, for example, in our input box.
  • output this new variable to the console
  • added a new link variable and constructed it so that the search term is properly added to the link.
  • used the $http service to get the data from the URL defined in the link variable and printed the result to the console

Dependency injection

Now, if you try to run the app you’ll get this in your console log:

So, we see $http is not defined.

In case you’re familiar with Angular from before, you’ll immediately know that the problem here is that we’re using the $http service, but we haven’t dependency injected it in our controller. We do that simply by requiring it in the controller definition:

.controller('SearchCtrl', function($scope, $http) {

In case you’re not familiar with the Dependency injection concept, you can read a bit about it here.

Now, if you run the app and enter something in the search term and click the search button you’ll see something like this in your console log:

Saving the results for later use

Here you can see that we’re getting back the result object and that in it’s data property there are 25 objects, which hold information about the images that we want to show in our app.

But, how do we show these images in our application?

We see that the api call returns 25 images within the data object (which again is inside the data object), let’s save this in some variable for later use:

$scope.giphies = [];

And, let’s store the 25 objects from the api call to this variable:

$scope.giphies = result.data.data;

Just for reference, to put it all in one listing, the contents of the SearchCtrl controller should now be:

.controller('SearchCtrl', function($scope, $http) {
console.log("Hello from the Search controller");

$scope.search = {};
$scope.search.term = 'cats';
$scope.giphies = [];

$scope.performSearch = function (){
var searchTerm = $scope.search.term.replace(/ /g, '+');

console.log("search term: " + searchTerm);

var link = 'http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=' + searchTerm;

$http.get(link).then(function(result){
$scope.giphies = result.data.data;
console.log($scope.giphies);
});
};
})

Showing the images

This is all great now, but we don’t want to be logging out our objects to the console, we want to show them in our app.

After examining the result object for a while, we can conclude that to show the image, we need to position ourselves on the object images, then original, and finally on the url property.

To show one image we can add an img tag in the tab-search.html file and position ourselves on the first element of the giphies array, in the object images, then original, and finally on the property url. Again, this long fluff is way nicer in code:

<img ng-src="{{giphies[0].images.original.url}}">

And sure, we do a search, and we get one image shown:

But, we want all GIFs, not just one!

For that we will use Angular’s ng-repeat:

<img ng-repeat="g in giphies" ng-src="{{g.images.original.url}}">

And, sure enough, all 25 images are here now. But, we would like to to be, you know, a bit nicer as it’s not every day that you get to work on an enterprise application like this!

So, what do we do? We go in the Ionic documentation, and we search for a bit… Finally, we settle for the Card component.

Let’s copy it’s code to our tab-search.html but without the footer part, and let’s adjust it a bit by moving our img inside it:

<div class="card">
<div class="item item-divider">{{g.id}}</div>
<div class="item item-text-wrap"><img /></div>
</div>

By now you’re a pro at this, but let’s just note that we moved the ng-repeat from the img tag to the div tag with the card class, so that we get this nice Card component repeated for every new GIF. Oh, and the {{g.id}} is there so that we write something (the GIFs id in this case).

Styling

At this moment we’re super happy, but the designer in us notices that our images are not quite right. We’re rocking the design stuff and all that CSS thingies, so we know we just need to add the following CSS rule to the image:

width: 100%;

SASS

But, writing plain old CSS is soo 2013 (we’re in 2016, remember? ;)), and I want to show you how to set up your Ionic to work with SASS.

I won’t go into the details, but TL;DR would be that SASS is CSS on steroids which basically allows you to have functions, variable, mixins, etc… Learn more here.

Go back to the terminal where you have ionic serve --lab running from before and break the process (CTRL + C). The, execute the following command:

ionic setup sass

And, next step is. Oh, there’s no next step! All is set up for you! To not go into details; Gulp tasks, proper index.html includes, etc…

Now, you should write your SASS code in the scss folder. Let’s create a new file _custom.scss (the underscore is important!) here with the following code:

.card img {
width: 100%;
}

.centerText {
text-align: center;
}

And, do not forget to import it by appending this to the ionic.app.scss file (contained in the scss folder):

@import "custom";

Yes, you’re importing here without the underscore in the name.

If you want to center your text (where we display the id of the GIf), just add the class like this:

<div class="item item-divider centerText">{{g.id}}</div>

Now just run ionic serve --lab again and admire you’re app with nicely positioned title and nicely set up images:

Student becomes a master

Sure enough, you should congratulate yourself now; you have a working app! But, let’s be honest, putting all that code inside the controller seems a bit dirty, doesn’t it? Besides, we’re making an enterprise app here, and we want to have some structure and follow the proper practices, right?

Great, I’m glad you’re with me on this.

We’re going to create a service for fetching these GIFs. Our service (called Giphy) will look like this (yes, change the whole prior content of the performSearch function with this):

$scope.performSearch = function (){
Giphy.search($scope.search.term).then(function(result){
$scope.giphies = result;
});
};

Also, don’t forget to inject Giphy in the controller:

.controller('SearchCtrl', function($scope, $http, Giphy) {

Lets’ write a service

In the file services.js remove the Chats factory and add this instead:

.factory('Giphy', function($http) {
return {
search: function(term) {
var searchTerm = term.replace(/ /g, '+');

var link = 'http://api.giphy.com/v1/gifs/search?api_key=dc6zaTOxFJmzC&q=' + searchTerm;

return $http.get(link).then(function(result) {
return result.data.data;
});
}
};
});

Don’t be scared off by the strange code; it’s basically a boilerplate code for writing factories/services. What’s important here is that we defined a factory named Giphy and that we’re exposing the function search which accepts the parameter term.

You may notice that we basically copied the whole code from the controller to this factory.

The only difference is that here we’re returning the result of the $http.get call, which basically returns the promise, upon which we can call then in the controller. In case you don’t know, promises work in such a way that once the promise resolves (finishes with whatever task it had) it activates the then part where we then do something with the data that was returned via this promise.

Also, please note that we injected the $http service so that we could use it for our HTTP calls, just as we did in the controller before.

So what?!

rant

You run the app, and it’s not faster, it’s not nicer, it’s basically the same as half an hour ago, so what gives??

Well, with this you modularized your code so that now you’ll be able to use the Giphy service in any other controller, just by injecting it where needed. In case you didn’t have this, you would have to copy/paste the code through the controllers.

This would lead to code duplication, which would then lead to a maintenance nightmare, which again leads to compounded code debt, which in the end leads to a miserable life as a developer.

And, you don’t want this for yourself, as you’re an aspiring developer looking to get better each day. Forgive the cheesy metaphors, but really, these days I’ve seen too many people give up for smaller reasons than “I don’t know how to write a service/factory”.

But, I’m happy for you! Because making it this far in the tutorial puts you far ahead the pack. Keep learning and growing and you’ll do just fine in life!

/rant

Ionic.io and Ionic view

You can test the app in the emulator/simulator; you can test it by running it on your physical device. But also, you can test your app on your mobile phone via the Ionic View application.

Just download the app for your phone and create an account on Ionic.io.

Once you have the account, login via your terminal by executing:

ionic login

and, finally, upload the app to the Ionic.io service with:

ionic upload

Once this is done, you’ll be able to see your app in the Ionic View app on your phone and you can run it from there.

Get the code on Github

This is just a short rundown of the commands that you need to run to get your project to Github:

  • create a project on Github
  • Execute git init in the root of your project
  • git add .
  • git commit -m "my awesome app"
  • git remote add origin https://github.com/Hitman666/GiphySearch.git – make sure this is the correct url to your project on Github
  • git push -u origin master

Moar learning materials plz!

In case you’re interested in learning more, you can download a PDF version of unabridged 4 posts via Leanpub, or you can read the abridged (but still 10000+ words long tutorial here). Yes, you can choose zero as the amount and I promise I won’t take it against you 😉

Conclusion

Our goal on these meetups is to ‘meet’ each other, learn something new, help each other, and if nothing else to hang out with the people who hold similar interests.

The theme for next meetup will be ‘Introduction to Test Driven Development in JavaScript and Ionic’ (those inclined to get ready in advance can read this post). The date is yet to be defined, but somewhere around the end of August.

Ah, and finally, if you want to see how one would build the same exact application with Angular 2, you can check out the tutorial that I wrote for Pluralsight: Getting started with Angular 2 by building a Giphy search application.

See you!

Build an #Ionic app for searching #gifs using #Giphy API https://t.co/aBzRBVNeE3

— Nikola Brežnjak (@HitmanHR) July 14, 2016

Angular 2

Getting started with Angular 2 by building a Giphy search application

My first tutorial about Angular 2 was just published on Pluralsight.

You can read the post here: Getting started with Angular 2 by building a Giphy search application.

If you like the post, please spread the word, and if you’re on Reddit, please consider +1-ing it here

Let me know what you think in the comments, and until next time, keep learning!

Page 22 of 51« First...1020«21222324»304050...Last »

Recent posts

  • When espanso Breaks on Long Replacement Strings (and How to Fix It)
  • 2024 Top Author on dev.to
  • Hara hachi bun me
  • Discipline is also a talent
  • Play for the fun of it

Categories

  • Android (3)
  • Books (114)
    • Programming (22)
  • CodeProject (36)
  • Daily Thoughts (78)
  • Go (3)
  • iOS (5)
  • JavaScript (128)
    • Angular (4)
    • Angular 2 (3)
    • Ionic (61)
    • Ionic2 (2)
    • Ionic3 (8)
    • MEAN (3)
    • NodeJS (27)
    • Phaser (1)
    • React (1)
    • Three.js (1)
    • Vue.js (3)
  • Leadership (1)
  • Meetups (8)
  • Miscellaneou$ (78)
    • Breaking News (8)
    • CodeSchool (2)
    • Hacker Games (3)
    • Pluralsight (7)
    • Projects (2)
    • Sublime Text (2)
  • PHP (6)
  • Quick tips (41)
  • Servers (8)
    • Heroku (1)
    • Linux (3)
  • Stack Overflow (81)
  • Unity3D (9)
  • Windows (8)
    • C# (2)
    • WPF (3)
  • Wordpress (2)

"There's no short-term solution for a long-term result." ~ Greg Plitt

"Everything around you that you call life was made up by people that were no smarter than you." ~ S. Jobs

"Hard work beats talent when talent doesn't work hard." ~ Tim Notke

© since 2016 - Nikola Brežnjak