<< Back

Instructions for the Places Mobile App Project (Pro)

In this project you are going to create a mobile app like this that reads your geolocation and displays nearby places. Click to watch a playlist of videos for completing this project.

In this exercise we use Ionic Creator Pro to create the app. If you don't have an account yet you can sign up at Creator.Ionic.io. There is an education discount code on Laulima that you can use to upgrade to Creator Pro.

With Creator Pro you will use Creator to create the pages complete with the Angular directives. You will also add the page controller code and services that connect to the APIs. When you are done you will export the completed app to your website. Click here if would like to see how to complete the assignment without upgrading to the Pro version.


APIs

We will be using the following Application Programmer Interfaces (APIs) which augment Google's Maps and Places APIs. Click the URL for documentation on the underlying Google API that is used. The source code for these APIs is provided if you would like to implement the same or modified interface on your own server. Note that if you do, you may need a Header set Access-Control-Allow-Origin "*" line in an .htaccess file before any external client (e.g., Ionic Creator) can access your APIs.


Step 1. Create Pages with Components and Directives

Create Pages

Creator doesn't work on some browsers. Google Chrome seems to work well on both my Windows machine and my Mac. Safari on my Mac seems to work. Creator doesn't seem to work on Microsoft Internet Explorer or Edge browsers. Note that browsers get updated all the time.

We begin by creating the two pages used for this app. See Playlist Free or Playlist Pro depending on which Ionic Creator you are using.

  1. Create a new project named "Places"
    1. Click +New Project
    2. Name your project: Places
    3. Click the first (blank) project type
    4. Click Create Project
  2. Edit Page 1
    • Title = Places
  1. Add Page 2 - Click +Add Page above the left Pages panel
    • Click to create a Blank page
    • Set Title = Details
    • Route Parameters
      • Click +Add twice to add route parameters
      • Param name... place_id = (leave Default value blank)
      • Param name... price_level =

Page 1 Components

In the Pages panel in the upper left, click the Places page to edit the first page. Add the following components. (See Video 2.)
  1. Add a Container - First row
    • Tag = div
    • Classes = row
    1. Add a Container
      • Tag = div
      • Classes = col-50 col-bottom
      • Paragraph
        • Content = Enter Address or tap **Where Am I?**
    2. Add a Container - "Where Am I?" button
      Hint - Above the properties panel on the right, click to select the container that holds the paragraph. Duplicate the left container, then edit the right container and deleting what is not needed.
      • Tag = div
      • Classes = col-50
      • Add a Button
        • Link = Page (first icon)
        • Type = Not Linked
        • Text = Where Am I?
        • Angular Directive
          • Click +Add to add a new directive
          • ng-click = readGeolocation()
  2. Add an Input
    • Title =
    • Type = Text
    • Placeholder = Enter Street Address
    • Angular Directives
      • Click +Add twice to add 2 new directives
      • ng-model = data.address
      • ng-blur = getLocation()

Page 1 - Places (continued)

Continue by adding these components on the first page under the Street Address Input component.
  1. Add a Paragraph
    • Align = Center
    • Content = **{{ data.latlng }}**
  2. Add a Select
    • Title = Place Type
    • Angular Directives
      • Click +Add three times
      • ng-model = data.type
      • ng-options = type.TypeID as type.TypeName for type in types
      • ng-change = getPlaces()
  3. Add a Paragraph
    • Align = Center
    • Content = {{ data.num_places }}
  4. Add a Avatar List Item - Hint: click on the List Item component to edit the properties.
    • Link = Page (first icon)
    • Type = Details - /page2
    • place_id = {{ place.place_id }}
    • price_level = {{ place.price_level }}
    • Switch to text input (not upload mode)
    • Avatar Image = {{ place.icon }}
    • Text Content = {{ place.name }}
    • Text Line #2 = {{ place.vicinity }}
    • Right Side Icon = ion-ios-arrow-right
  5. Edit the List Container containing Avatar List Item - Hint: above the Avatar List Item properties panel on the right, click List to edit the list container properties.
    • Angular Directive
      • ng-repeat = place in places
  • Done with Page 1

Page 2 - Place Details

In the Pages panel in the upper left, click the Details page to edit the second page. Add the following components. (See Video 3.)
  1. Add a Heading
    • Text = {{ post.result.name }}
    • Size = H3
    • Align = Center
  2. Add a Paragraph
    • Align = Center
    • Content = **{{ post.result.vicinity }}**
  3. Add a Container - Star Rating and Dollar Sign Price Level
    • Tag = div
    • Classes = row
    1. Add a Container
      • Tag = div
      • Classes = col-50
      1. Add a Paragraph
        • Content = **Rating**
        • Angular Directive
          • ng-hide = hideRating
      2. Add a Container - below paragraph
        • Tag = div
        • Classes = row
        • Angular Directive
          • ng-hide = hideRating
        1. Add a Container
          • Tag = div
          • Classes = col-25
          • Add a Paragraph - inside col-25 container
            • Color = #E42112
            • Align = Center
            • Content = {{ post.result.rating }}
        2. Add a Container - Star Icons
          Hint - Duplicate the col-25 container containing the paragraph on the left, then edit the duplicated container right container and deleting what is not needed.
          • Tag = div
          • Classes = col-75
          • Add an Image - inside the col-75 container
            • Link = Page (first icon)
            • Type = Not Linked
            • Source = Switch to text input (not upload mode)
            • Image Source = {{ data.star_rating_icon }}
            • Style Size = 100% x auto
            • Style Position= Center
    2. Add a Container
      Hint - Duplicate the col-50 container containing the rating and rating icons, then edit the right container and deleting what is not needed.
      • Tag = div
      • Classes = col-50
      • Angular Directive
        • ng-hide = hidePriceLevel
      1. Add or modify Paragraph
        • Content = **Price Level**
      2. Add or modify Container - Price Level
        • Tag = div
        • Classes = row
        1. Add or modify Container
          • Tag = div
          • Classes = col-25
          • Add or modify Paragraph
            • Color = #E42112
            • Align = Center
            • Content = {{ post.result.price_level }}
        2. Add or modify Container - Dollar Sign Icons
          • Tag = div
          • Classes = col-75
          • Add or modify Image
            • Link = Page (first icon)
            • Type = Not Linked
            • Source = Switch to text input (not upload mode)
            • Image Source = {{ data.price_level_icon }}
            • Style Size = 100% x auto
            • Style Position= Center
  4. Add a Spacer
    • Height = 50px - When adding the rest of the components, always keep this spacer component at the very bottom

Page 2 - Place Details (continued)

Continue on the second page. Add the following components below the Rating and Price Level icons and above the 50px spacer component.
Keep the 50px spacer at the very bottom.
  1. Add a Container
    • Tag = div
    • Classes = row
    • We will make the More... button first, then duplicate it for the Phone button.
    1. Add a Container - More ... Button to Google Places Page
      • Tag = div
      • Classes = col-34
      • Add a Button
        • Link = Link (second icon)
        • Type = {{ post.result.url }}
        • Text = More ...
        • Align = Center
        • Icon = ion-ios-arrow-right
        • Icon Position = align right
    2. Add a Container - Phone Number Button (left of More... button)
      Hint: Duplicate the col-34 container containing the button, then edit the left col-34 container and deleting what is not needed.
      • Tag = div
      • Classes = col-66
      • Angular Directive - This adds space between the buttons and is not really an Angular directive
        • style = padding-right:20px
      • Add or modify Button
        • Link = Phone (last icon)
        • Type = {{ post.result.formatted_phone_number }}
        • Text = {{ post.result.formatted_phone_number }}
        • Align = Center
        • Icon = No Icon
        • Angular Directive
          • ng-hide = hidePhone
  2. Add a Container - Hours, Open, Closed Row
    • Tag = div
    • Classes = row
    1. Add a Container
      • Tag = div
      • Classes = col-50
      • Add a Paragraph
        • Content = **Hours**
        • Angular Directive
          • ng-hide = hideHours
    2. Add a Container - Hint: Duplicate the col-50 container containing the **Hours** paragraph, then edit the right col-50 container.
      • Tag = div
      • Classes = col-50
      • Add or modify Paragraph
        • Color = #E42112 (red)
        • Align = Right
        • Content = **Open**
        • Angular Directive
          • ng-hide = hideOpen
      • Add a Paragraph - Hint: Duplicate the previous Paragraph and edit.
        • Color = #969292 (gray)
        • Align = Right
        • Content = **Closed**
        • Angular Directive
          • ng-hide = hideClosed
  3. Container - Open Hours Daily
    • Tag = div
    • Classes =
    • Angular Directives
      • style = padding-left:10px
      • ng-hide = hideHours
    • Add Html
      • <span ng-repeat="hour in post.result.opening_hours.weekday_text">
        {{ hour }}<br>
        </span>
  4. Add a Paragraph
    • Content = _____
  5. Add a Slider - Static Maps (4 at different zoom levels)
    After placing the Slider, click Slider above the right panel to be sure you begin by editing the Slider and not the first slide.
    • Style Size = 100% x 300px
    • Click Add New Slide (Slide 4)
      • Click on and delete the Slide 4 Heading (on the slide)
      • Switch to text input (not upload mode)
      • Enter {{ data.map4 }}
    • Edit Slide = Slide 1
      • Title = Slide 1
      • Switch to text input (not upload mode)
      • Enter {{ data.map1 }}
    • Edit Slide = Slide 2
      • Title = Slide 2
      • Switch to text input (not upload mode)
      • Enter {{ data.map2 }}
    • Edit Slide = Slide 3
      • Title = Slide 3
      • Switch to text input (not upload mode)
      • Enter {{ data.map3 }}
  6. Add a Paragraph
    • Align = Center
    • Content = Swipe to Zoom
  7. Add a Paragraph
    • Content = _____

Step 2. Add Page Controller Code

See Video 4 Pro.

Page 1. Places Page

  1. Click the Code tab in the lower left hand corner to raise the code window
  2. On the left, under Pages, click Places
  3. Replace (line 4): function ($scope, $stateParams) {
    with the following lines of code:

    function ($scope, $stateParams, GetPlaces, GetAddress, GetLocation, GetTypes, GetPlace, Globals) {
        
        var key = Globals.key;
        
        $scope.data = { "type": "restaurant" };
    
        GetTypes.getPost()
        .then(function(response) {
            $scope.post = response;
        
            $scope.types = $scope.post.PlaceTypes;
        });
        
        var geo_options = { enableHighAccuracy: true, timeout: 10000, maximumAge: 0 };
        
        function error_alert(err) {
          var msg='Geolocation ERROR(' + err.code + '): ' + err.message;
          alert(msg);
        }
        
        $scope.readGeolocation = function()
        {
            navigator.geolocation.getCurrentPosition(savePosition, error_alert, geo_options);
            setTimeout(readGeolocationAgain,1000);
        }
        function readGeolocationAgain()
        {
            navigator.geolocation.getCurrentPosition(savePosition, error_alert, geo_options);
            $scope.getAddress();
        }
        function savePosition(location)
        {
            var latitude = location.coords.latitude.toFixed(6);
            var longitude = location.coords.longitude.toFixed(6);
            $scope.data.latlng = latitude+','+longitude;
        }
        
        $scope.readGeolocation();
        
        $scope.getAddress = function()
        {
            GetAddress.getPost($scope.data.latlng, key)
            .then(function(response) {
                $scope.post = response;
                $scope.data.address = $scope.post.results[0].formatted_address;
            });
            $scope.getPlaces();
        }
    
        $scope.getLocation = function()
        {
            GetLocation.getPost($scope.data.address, key)
            .then(function(response) {
                $scope.post = response;
                $scope.data.address = $scope.post.results[0].formatted_address;
                var latitude = $scope.post.results[0].geometry.location.lat;
                var longitude = $scope.post.results[0].geometry.location.lng;
                $scope.data.latlng = latitude+','+longitude;
                $scope.getPlaces();
            })
        }
    
        $scope.getPlaces = function()
        {
            GetPlaces.getPost($scope.data.latlng, $scope.data.type, key)
            .then(function(response) {
                $scope.post = response;
                $scope.places = $scope.post.results;
                $scope.data.num_places = $scope.post.results.length + " places returned";
            })
        }
  4. To close the Code window, click the down arrow at the top right of the Code window.

Page 2. Details Page

  1. Click the Code tab in the lower left hand corner to raise the code window
  2. On the left, under Pages, click Details
  3. Replace (line 4): function ($scope, $stateParams) {
    with the following lines of code:

    function ($scope, $stateParams, GetPlace, Globals) {
        
        var key = Globals.key;
        
        $scope.data = {
    		"placeid" : $stateParams.place_id,
    		"price_level" : $stateParams.price_level,
    	};
    	
        GetPlace.getPost($scope.data.placeid, key)
        .then(function(response)
        {
            $scope.post = response;
    
            if(!$scope.post.result.formatted_phone_number) $scope.hidePhone = true;
    
            if($scope.post.result.opening_hours)
            {
                if($scope.post.result.opening_hours.open_now) { $scope.hideClosed = true; }
                else $scope.hideOpen = true;
            }
            else
            {
                $scope.hideClosed = true;
                $scope.hideOpen = true;
            }
    
            if($scope.post.result.rating)
            { 
                $scope.data.star_rating_icon = "https://secrdir.com/api/rating_icons?stars=" + $scope.post.result.rating;
            }
            else $scope.hideRating = true;
            
            if($scope.post.result.price_level)
            {
                $scope.data.price_level_icon = "https://secrdir.com/api/rating_icons?dollars=" + $scope.post.result.price_level;
            }
            else $scope.hidePriceLevel = true;
    
            var latlng = $scope.post.result.geometry.location.lat;
            latlng += ',' + $scope.post.result.geometry.location.lng;
            
            var map_url = "https://secrdir.com/api/staticmap?key=" + key;
            map_url += "&center="+latlng;
            
            $scope.data.map1 = map_url + "&zoom=12";
            $scope.data.map2 = map_url + "&zoom=14";
            $scope.data.map3 = map_url + "&zoom=16";
            $scope.data.map4 = map_url + "&zoom=18";
        });
  4. To close the Code window, click the down arrow at the top right of the Code window.

Step 3. Add Services

  1. Click the Code tab in the lower left hand corner to raise the code window
  2. On the left, under Other JS, click services.js
  3. Copy the following services just below the top line: angular.module('app.services', [ ]) (line 1),:

    .service('Globals',[function(){
        var ret = { "key": "MY_GOOGLE_API_KEY" }
        return ret;
    }])
    
    .service('GetPlaces', function($http){
        return {
            getPost: function(location, type, key) {
                var query = "?location="+location;
                query += "&key=" + key;
                query += "&type=" + type;
                query += "&rankby=distance";
                return $http.get("https://secrdir.com/api/place/nearbysearch/"+query)
                .then(function (response) {
                    return response.data;
                })
            }
        }
    })
    
    .service('GetPlace', function($http){
        return {
            getPost: function(placeid, key) {
                var query = "?placeid="+placeid;
                query += "&key=" + key;
                return $http.get("https://secrdir.com/api/place/details/"+query)
                .then(function (response) {
                    return response.data;
                })
            }
        }
    })
    
    
    .service('GetAddress', function($http){
        return {
            getPost: function(latlng, key) {
                var query = "?latlng="+latlng;
                query += "&key=" + key;
                return $http.get("https://secrdir.com/api/geocode/"+query)
                .then(function (response) {
                    return response.data;
                })
            }
        }
    })
    
    .service('GetLocation', function($http){
        return {
            getPost: function(address, key) {
                var query = "?address="+address;
                query += "&key=" + key;
                return $http.get("https://secrdir.com/api/geocode/"+query)
                .then(function (response) {
                    return response.data;
                })
            }
        }
    })
    
    .service('GetTypes', function($http){
        return {
            getPost: function() {
                return $http.get("https://secrdir.com/api/place/types/")
                .then(function (response) {
                    return response.data;
                })
            }
        }
    })
  4. To close the Code window, click the down arrow at the top right of the Code window.

Step 4. Test Your App in Creator

Before you export your app to your website you want to make sure it works in Creator. I recommend using Google Chrome.
  1. In the Pages panel in the upper left corner, click the Places page to make sure the Places page is in the center window (not the Details page).
  2. Above the center window, click the Preview (eyeball) icon.
  3. Your browser should ask if it is OK to access your location. Click OK or Allow.
  4. Tap the Where Am I? button and your address should fill in the Input box.
  5. Test tapping on different restaurants. Some may be missing star or price level ratings. Test to make sure both ratings display properly.
  6. Test tapping the More... button on the Details page to see that the Google Place page appears correctly.
  7. Swipe to view the four different zoom levels of maps.
  8. When you are satisfied your app works properly, you can export it to your class website.

Step 5. Export Your Mobile App from Ionic Creator to your Website

See Video 5 Pro. To export your app to your class website, do the following:
  1. Click on the Cloud icon with the down arrow to export your application to your computer as a ZIP file.
  2. Open your cPanel on your web server.
  3. In the cPanel File Manager, click into the public_html directory and create a new folder named places.
  4. Click to open the places folder. Upload the zipped folder of your app into the places folder on your server.
  5. Click to select the zipped file, right click and unzip the file. Refresh the folder to see all the files and folders that were unzipped.
  6. Before your app will be visible, you need to change the access permissions of the lib folder. Do so as follows:
    1. In the /places/ folder, create a new file named chmod.php
    2. Enter the following line: <?php exec('chmod -R 755 lib'); echo "Done"; ?>
    3. Save the file and close the file.
    4. On your browser, go to https://xxxxxx.secrdir.com/places/chmod.php where xxxxxx is your subdomain. The word Done should display.
  7. Test your app by going to https://xxxxxx.secrdir.com/places/ on your browser or mobile device. Scale the width of the browser if you wish. It should work the same as it did on Creator.

Richard Halverson, Jr., Ph.D. • University of Hawaii