Create a Location-Aware Site with Sencha Touch - Displaying Locations

This tutorial will guide you through the development of a Location-based mobile website using the Google Place search engine and Sencha Touch 2.1 . This is the second in a two-part series, and today we'll learn how to display map markers and location details.

This tutorial is the second and final part of our previous post Create a Location-Aware Site using Sencha Touch. In Part 1, we learned how to retrieve different local businesses like restaurant, hospitals, or theaters near our user's current location. We did this using HTML5 geolocation and the Google Place Search API.

In this section, we'll cover the following topics:

How to maintain browser history using Sencha router.

How to display multiple markers for each place in the Google map and, when selecting a marker, show an Info bubble with place information.

How to show complete details of each place, their reviews, and how to show the place images in a Pinterest style gallery.

We will resume where we left in the previous post.

1. Layout Change

In the first post, the app we developed was fairly simple by its navigation and a Sencha Navigation View was perfect for such a layout. However, while you find that you have to add more custom controls to your toolbars, using this type of view becomes difficult because Sencha maintains the Navigation bar on its own and showing/hiding items in it increases complexity.

So, as we proceed to add more functionality in the app, we need to make couple of changes in the structure. First, we will change the Main panel to a Card layout.

Previously we were showing all the places as a list in PlaceList List view. Now, we want to add a Map option as well that will display all the place positions as markers. So, we change the second page to a Card layout which holds two panels - PlaceList and PlaceMap:

Places (Card layout)

PlaceList (Sencha List View)

PlaceMap (Sencha Map Container)

Places.js

PlaceList.js

The Places container has PlacesList panel as its children. Because we omitted the Navigation View, we have to add individual TitleBar to each child panel. Also, we add a map icon to the right of the toolbar; tapping this button, we show the Map panel with all the place markers.

Notice the map icon at top right corner. Tapping this, we create and activate the Map panel. Let's create a PlaceMap view which will be a simple container with the map and a toolbar which has another button to come back to PlaceList.

Now, in the controller we will add three functions to create and activate these panels. I always prefer to have an individual navigation function for each page that separates the navigation from the functional part.

showHome()

While a category item is pressed, we create the Places container, set the title of each panel to the name of that category and activate the PlaceList panel within. The last part is necessary so that every time you land to the place list page from Home page.

You should now be able to test the app in browser and see how the layout change is working. We are done with the views related to multiple places. We will look into the placing the map markers soon but before that I want to cover how we can maintain browser history in these single page websites.

2. Maintain Browser History

While creating a single page website, it becomes absolutely necessary to maintain the browser history. Users navigates back and forth in a website and many times network issues or unresponsiveness forces the user to reload a page again. In these scenarios, if history is not maintained, refreshing a page will eventually bring the user back to the first page which becomes really annoying in certain cases.

In this section we will see how we can use Sencha's routing functionality to find a solution to this problem. Always remember to incorporate history maintenance from the start of your application. Otherwise, it will be a tedious job to implement this functionality while your app is already half done.

We will add two routes for the Categories and Places pages respectively.

routes: {
'': 'showHome',
'categories/:type': 'getPlaces'
}

Sencha Controller has a method named redirectTo which accepts a relative url and calls the function matched to that. We will use an empty string to go to the first page (i.e the Categories list) and a specific category type to go to the Place list page.

So, while we want to come back to the Categories list from the Places container, instead of calling the showHome() function, we just redirect to the specific route and it will automatically call the required method.

backToHomeBtn: {
tap: function () {
this.redirectTo('');
}
}

You may question: what difference is there between these two while both can be achieved in a single line of code? Basically, there is no difference but while you are going to maintain all the navigation via routing, I prefer to follow a single trend throughout. In addition, you have to maintain the window hash yourself if you call the function directly.

We saw the loadPlaces() method in part 1 of this tutorial which sends an Ajax request to retrieve the places data. This time we are going to add a bit there for saving the current category type in the controller instance which will be needed in future. Also, we want to disable the map button until all the places are loaded. We want to display the marker as soon as the map is rendered and for that, the places should be loaded first.

In launch(), we add an event "categorychange" to the Application object and fire it once we get a new category type. It sets a Controller variable activeCategoryType to that type. Now the question is, why do we set the variable via an event than assigning it directly? It is because we may want to set the type from other views or controllers. Doing it this way also increases the feasibility a lot.

Hence, for the category list item tap event also, we are not going to call the getPlaces() function directly. Rather, we will use the redirectTo method to load places for that particular category type. Let's add that in Controller's control:

So, while you select a category type, say "art_gallery", your browser url changes to "/locator/locator/#categories/art_gallery" and a PlaceList panel will open with all the Art Galleries. Now, if you reload the page, it will again open the same PlaceList page - not the first page.

3. Display Places on the Map

We are done with all the views needed to show the places. We will create and show the PlaceMap container every time the user taps the Map button. We do not want to render the map unless the user wants to see it.

Also, instead of removing the markers one by one and adding others, I prefer to remove the map panel completely once the user comes back to the category listing page. So, let's add this part for the home button tap:

Create Markers and Show Them

Showing markers is pretty easy. Just instantiate a marker passing the position and google map instance. You can also use a different image in the marker like what we did here. For each record in the Places store we create a marker and on mouse down (which actually works as tap event) we call the setupInfoBubble() method passing the data and the marker instance.

Show the User's Current Location

We retrieve the user's current location in the getPlaces() method and save it in the Util singleton class. We create a different marker for that position.

Show Infobubble While Tapped on a Marker

Google map's utility library has a great component named Infobubble for showing custom info windows. You may get a detailed discussion on the implementation at iPhone like infobubble with Sencha Touch. Mostly, we create a new instance of InfoBubble with requires new config options. Because only one infobubble is shown at a certain point of time, we just replace the content of infobubble every time it is opened. We keep a reference to the place's "reference" property which will be required to go to the PlaceDetails page.

On tapping the infobubble, we redirect the browser to a url like categories/:type/:reference. We will handle this part in another controller dedicated to place details functionality.

4. Place Details

The Place Details page opens up while the user selects a place either from list or from map. I categorized the details in 3 parts - Info, Image Gallery and Reviews. A TabPanel is most suited for such a layout. Let's see how the Views can be structured:

Views

details.Main

details.Info

details.Gallery

details.GalleryCarousel

details.Review

Controller

PlaceDetails

We create a another subset of namespace and put all these files inside the "details" directory of the "view" folder. Also, we create a separate controller that maintains only place details related functions. Remember to add all these views and controller in the app.js file.

View/Details.Main

The main panel is a simple tab panel with three child panels and a titlebar.

The DIV element with the "map" css class is used later to render the Google Map. I used different styling in all the details pages - I am not including all those CSS details in this tutorial content to keep it tidy. You will get all the CSS properly structured in the project files.

The info view is created. Now, we have to load the place details and apply its data in an Info page template. Let's define the PlaceDetails controller.

We will need a showDetails method which will create and open the details.Main tab panel. While selecting a particular place, we have both its category type and place reference. We need to store both these values - the place reference will be used to get further details of the place and the category type will be needed if user directly opens up a place details page and then try to go back to the places list page.

We fire both the "categorychange" and "placechange" event on the Application object to save the type and the reference. Also, we call the loadPlaceDetails() method passing the places response to retrieve the complete information of that place.

The images for a place do not come automatically - rather, all the images are to be requested separately with their reference, api key and dimension. Complete details on it can be found here. We store all the photo urls in a "photos" property of the Place data.

Once we get the place details object, we apply the result to all the child container of the Tab Panel because all of them are utilizing Sencha's template fnctionality. Also, we call showPlaceLocation() method which creates a Google map, renders it in Info panel and display a marker for the place inside the map.

Place Image Gallery

We will create a Pinterest style image gallery for the Place images and show the full view of the images in a carousel. I have developed a component for the same and wrote a detailed blog post on it which you can find here Mosaic image gallery with Sencha Touch. The same components are used here - just the data properties are different.

Here is how the gallery looks like. -webkit-column property of CSS3 is used here to get a mosaic like layout for the images.

Place Reviews

Place reviews is a simple list of the reviews - which I utilized with a plain Sencha container instead of a list view. Adding some custom styling becomes easy here and also makes it look cool. We used the same rating system to show individual ratings.

The profile images of the users are their Google+ user profile image. We use a set of custom template functions to edit the content on the fly.

From the template and XTemplate functions, we notice a number of new things:

Default User Profile Image

We add an onerror event on the IMG tag. This event gets fired if the src attribute is empty or null. We create a function onBrokenProfileImage() in our Util file and call it if there is any error and set the src attribute to a default user image.

I got this function somewhere in the web while browsing. It works like a charm.

Expand/Collapse Reviews

Most of the user reviews are large chunk of texts and showing full text in a list is not very mobile friendly. So, we add an option to show/hide a full review if it has more than 120 characters. We add a XTemplate method applyExpandable() which checks the length of the text and add a "more" link to the end of the text after ellipsis.

On singletap on the review panel's element, we capture the event and if the text is already collapsed, expand it with a "less" link and vice versa with a "more" link. We use the reviewer profile link to wrap both the user image and name - so, clicking it will open user's profile in Google+.

5. A New Theme

Probably you have already noticed that I changed the theme from my previous version of the app. It was because the background of the app is dark and a lighter toolbar color will make it look nice. Also, I used dark colored buttons in toolbar for a better contrast. Here is the SASS file I used:

This is it. We are done with all the pages for the application and now it is a full fledged mobile website. You can try to wrap it with Phonegap and it should work as a native app as well without any issue.

Conclusion

In this final post of the series we covered a number of interesting topics which include:

Sencha routing

Display map with markers

iPhone like Infobubble

Mosaic image gallery similar to Pinterest layout

Along with these, we learnt how with CSS we can make an app look beautiful and professional. Do notice how we structured the application at start, used lazy rendering of components whenever required, destroyed a component while its not in use and commented our code thoroughly. These things improves your app's performance and coding standards a lot. Happy coding!