Advertisement

Creating a Mobile Event Calendar With DHTMLX

by

This tutorial describes how to build an HTML5-based mobile calendar to track conferences and events that run on iOS and Android phones using a mobile version of dhtmlxScheduler (open source, GPL). At the end, users will be able to add and edit events, select the conference location on Google Maps, and see the events in day, month, or list views.


Project Overview

The final demo includes the event calendar (we'll also call it the scheduler) that displays events in three standard views: list, day, and month. While the end user adds, deletes, or edits events the calendar app saves the changes to the server and updates the database. The demo also automatically detects the locale in use and adapts the calendar interface accordingly. The end user can set the event location on Google Maps in two ways: typing in the address or pointing it on Google Maps.


1. Get Started

First, download the latest package of the mobile version of dhtmlxScheduler. It is based on the DHTMLX Touch framework and distributed under an open source license (GNU GPL).

The mobile DHTMLX scheduler has a so called multiview-based architecture. It displays a set of screens that switch between each other. Each screen is a singular view where you can add any content. By default, the scheduler contains the following views:

  • Event preview screen (displays a short info about the event)
  • Details form (used to edit the event details)
  • Start/end edit form (built-in datepickers to choose the start and end dates of the event)
  • Calendar views: List (Agenda), Day, and Month

The predefined views are customizable (you can find the default definitions in the documentation). Also, there is a possibility to add new custom views.


2. Rendering an Event Calendar on a Page

Step 1

We'll start with the required code files to display the mobile scheduler on a page: dhxscheduler_mobile.js and dhxscheduler_mobile.css.

Step 2

Add the following code to your page:

    dhx.ready(function(){
            dhx.ui.fullScreen();
            dhx.ui({
                view: "scheduler",
                id: "scheduler"
            });
     });

Here is what these lines of code do:

  • calling the dhx.ready() function guarantees that the code placed inside is called after the page has been completely parsed, protecting it from potential errors. This is optional but I encourage you to use it.
  • dhx.fullScreen() activates the full-screen mode.
  • dhx.ui({}) is an object constructor for the scheduler (used in the DHTMLX Touch library).
  • view: "scheduler" - sets the component to render ('scheduler' is the hardcoded name of the mobile scheduler).
  • id is an optional parameter that sets the object id. We specified this parameter as we are going to refer to this object later.

After we've included the required files and added the code above, the calendar is rendered on a page:

Mobile Event Calendar - Month View

The scheduler has a neat and simple UI so you can use it as is. It's flexible enough so you can configure its look and feel to match your design needs, if you want to.


3. Loading and Saving Data

Step 1

To load data to the calendar, we will use the load() method. As we're going to load data from the server, we should set the only parameter (the path to the server script).

To provide the ability to save data back to the server, we need to define one more parameter in the object constructor - save. We also need to specify the same server-side script that we have specified in the load() method.

    dhx.ui({
                  view: "scheduler",
                id: "scheduler",
                save:"data/data.php"
    });
    $$("scheduler").load("data/data.php");

To refer to the scheduler through its id, we use the $$("scheduler") record.

To refer to the scheduler views (or views elements), you should write a complete dependency property inheritance chain: $$('scheduler"').$$('view_id').$$('viewElement_id')... You can check the elements ids in the related documentation.

Step 2

The data.php file contains the client-server communication logic to integrate with the server side. To define the logic, we will use a special helper dhtmlxConnector that implements all the routine (there are versions for Java, .NET, PHP, and ColdFusion, and it's free to use with the dhtmlx UI widgets). You can get more details about the use of dhtmlxConnector here.

We will use the PHP version and create the following code in the data.php file:

    <?php
         include ('../connectors/scheduler_connector.php');                
         $res=mysql_connect("localhost","root","");
         mysql_select_db("schedulertest");                  
         $scheduler = new JSONSchedulerConnector($res);
         $scheduler->render_table("events_map","event_id","start_date,end_date,event_name,details,event_location,lat,lng");
    ?>

The lines of the code do the following:

  • The first line includes the required code file.
  • The next two lines establish a connection to the server and open the specified SQL database.
  • The last two lines create a connector object ($scheduler) and configure data to retrieve.

In the attached demo package, you can find the dump file of the 'schedulertest' database.

As we complete this step we have a calendar populated with our demo data:

Mobile Event Calendar - Loaded with Data

4. Localization

Step 1

With this step we will make our calendar able to adapt to a particular language and region.

First, we need to specify a locale object (in the library it has the hardcoded name - 'locales') that will define all labels used in the calendar.

We will create an object in a separate file not to "overcrowd" the main .html file. The file will look like this:

    var locales  ={
        "de": {...},
        "en": {..},
        ...
    }

To see the full version of the 'locales' object, open the locales.js file included in the 'codebase' folder of the download package. In our demo, we have included the locales for just two languages (English and German) as an example. If needed, you can add the locale of any other language in this file.

Step 2

Then we include the locales.js file on the page:

<script charset="utf-8" type="text/javascript" src="../mobile_scheduler/codebase/locales.js"></script>

Next add the following code to the html file:

    //put this code at the very beginning of the page and not into the dhx.ready() function
    var locale = (navigator.language || navigator.systemLanguage || navigator.userLanguage ||'en').substr(0, 2).toLowerCase();
            if (!locales[locale])
                locale = 'en';

            scheduler.locale.labels=locales[locale];
            dhx.Date.Locale = locales[locale].calendar;

The lines of the code do the following:

  • The first line gets the language version of the browser.
  • The next few lines set the locale depending on the value returned by the first line.
  • scheduler.locale.labels sets the locale for common labels in the scheduler.
  • dhx.Date.Locale sets the locale for calendars used in the scheduler.

The calendar with a German locale looks like this:

Mobile Event Calendar - German Locale

5. Displaying an Event Location on Google Maps

Wouldn't it be great if users could see the place where an event happens? Here is the list of steps required to provide such an opportunity in your app:

  • Create a separate view for Google Maps
  • Add a button, by clicking on it the user will open the Maps view
  • Add an 'onclick' handler function that will be responsible for displaying Google Maps with the appropriate event marker on it
  • Add the event location info into the event preview screen

Step 1

We start by creating a Maps view. For our first step we'll include one more file on the page:

<script type="text/javascript" src="http://maps.google.com/maps/api/js?libraries=places&amp;sensor=true"></script>

Then we need to add a new view that will display Google Maps. DHTMLX Touch library has the 'googlemap' component that makes integration with Google Maps pretty easy (related documentation).

Here is our Google Maps view:

scheduler.config.views.push({
            id:"locationView",
            rows:[
                {
                    view:"toolbar",
                    css:"dhx_topbar",
                    elements:[
                        {
                            view:'button',
                            inputWidth: 100,
                            css:"cancel",
                            label: scheduler.locale.labels.icon_back,
                            click: "$$('scheduler').$$('views').back()"
                        }
                    ]
                },
                {     view:"googlemap",
                    id:"mymap"
                }
            ]
        });

Some explanations about the above code:

  • scheduler.config.views.push command adds a new view to the existing scheduler multi-view collection.
  • rows:[{}] arranges elements vertically. Each object is a separate row. The view consists of a toolbar and Google Maps.
  • $$('scheduler').$$('views') refers to multi-view object. The back() method switches multi-view to the previously active view.

The Map view will look like this:

Mobile Event Calendar - Map View

Step 2

We now need to add a button on the toolbar. The most appropriate view is the one that displays details for the selected event.

//place this code after the 'localization' block (step 4)
scheduler.config.selected_toolbar = [
            {view:'button', inputWidth:scheduler.xy.icon_back, css:"cancel", id:"back",align:"left",label:scheduler.locale.labels.icon_back},
            {view:'button',  width:100, id:"location",align:"right", label:scheduler.locale.labels.label_location, click:"showLocation"}, //our new new button
            {view:'button',  width:70, id:"edit",align:"right",label:scheduler.locale.labels.icon_edit}
        ];

The code above is the default definition of the toolbar (you can find it in the library's documentation) and our new button named as Location.

As we localize our app, all labels we add must be named in some way and added to the locales.js file. For example, now we'll add a button with the name 'Location'. So, in the locales.js file we add parameter label_location:"Location" and then set the button's attribute label to scheduler.locale.labels.label_location value.

The click attribute sets the name of a handler function that will be invoked on button clicks.

This is how the screen with event details should look now that we added the Location button:

Mobile Event Calendar - Location Button

Step 3

Before going to the main code, let's add to the page a variable named as 'marker' and assign it to the Google Maps marker instance. We define this variable as global because we need to have only one marker on the page (our events must have only one location).

//put this code at the very beginning of the page
 var marker = new google.maps.Marker({});

Executable function, or the 'onClick' handler, contains the following code:

function showLocation(){
            if (marker!=null){
                /*shows the view of multiview*/
                $$("scheduler").$$("locationView").show();
                /*necessary to resize google map*/
                $$("scheduler").$$("locationView").resize();

                /*event data*/
                var eventId = $$("scheduler").getCursor();
                var item = $$("scheduler").item(eventId);

                /*LatLng point*/
                var point = new google.maps.LatLng(item.lat,item.lng);

                var map = $$("scheduler").$$("mymap").map;
                map.setZoom(6);
                map.setCenter(point);
                google.maps.event.clearListeners(map, "click");

                marker.position= point;
                marker.map = map;
                marker.title = item.event_location;
                marker.setMap(map);
            }
        };

Again, let me explain what these lines of code do:

  • The first lines show the view created on the first sub-step and resizes it to fill the whole screen.
  • The next lines get the object of an event that the cursor is currently on. Note, the DHTMLX library uses a 'cursor' conception in its inner logic. So, to ensure the correct processing, you should operate with the 'cursor' while getting the currently selected item. In most cases, the getCursor() method returns the currently selected item. There is only one exception: when you delete an event, the scheduler removes the selection but keeps the cursor and it points to the non-existent event. Be careful with this!
  • The second line uses the Google Maps API to create a point based on the specified coordinates (the event coordinates that are stored in the database). Learn more about Google Mas API.
  • $$("scheduler").$$("mymap") refers to the 'googlemap' view. The "map" property returns the object of Google Maps.
  • The last lines zoom, center the map, and set the marker in the specified point.

Step 4

To add location information to the preview screen, we should redefine the default screen template. So that we do not have to rewrite the whole template (which is rather large), we'll use a trick:

    var default_temp =  scheduler.templates.selected_event;

    scheduler.templates.selected_event = function(obj)
    {
        var html = default_temp(obj);
    if (html!=""){
        html = html.replace(/<\/div>$/,"");
        html += "<div class='event_title'>"+scheduler.locale.labels.label_location+"</div>";
        html += "<div class='event_text'>"+obj.event_location+"</div>";
        html += "</div>";
    }
    return html;
    };

Here is what we have done with the above piece of code:

  • default_temp variable holds the default template of the screen.
  • 'wrapper' is a new div element to hold the location information.
  • 'event_text' is the predefined CSS class used in the default template, we use it to provide uniformity of the displayed event information.
  • scheduler.locale.labels.label_location is the label we added on the previous step ('Location', in the English locale).

Now the preview screen looks like this (with added location info):

Mobile Event Calendar - Event Preview Screen

6. Setting an Event Location on Google Maps

Now our app can display the location of an event. But what about editing the event location or setting the location for new events?

Now we need to allow the users to set/edit the event location and provide two different ways for typing the address in the text input and directly pointing on the map. This is what we need to do:

  • Add controls to the edit form
  • Provide handlers that process the incoming data

Step 1

We should add at least two controls to the event edit form: one is a text input to type the event address in and the second control can be a button, so that by clicking on it the user can open the map and set the point right on the map.

We will take the default edit form and then add the mentioned items (the list of available controls):

 scheduler.config.form = [
            {view:"text", label:scheduler.locale.labels.label_event, id:"text", name:'text'},
            {view:"text", label:scheduler.locale.labels.label_details, id:'details'},
            {view:"datetext", label:scheduler.locale.labels.label_start,    id:'start_date',name:'start_date', dateFormat:scheduler.config.form_date},
            {view:"datetext", label:scheduler.locale.labels.label_end,    id:'end_date',name:'end_date', dateFormat:scheduler.config.form_date},
            {view:"toggle", id:'allDay', label:"", options: [{value:"0",label:scheduler.locale.labels.label_time},{value:"1",label:scheduler.locale.labels.label_allday}], align: "right",value:"0"},
            //custom ‘location’ sections
            {view:"text", label:scheduler.locale.labels.label_location, id:"event_location"},
            {view:'button', id:"setLocation", label:scheduler.locale.labels.label_locate, click:"setLocation"},
            {view:"text", label:"Latitude", id:'lat', hidden:true},
            {view:"text", label:"Longitude", id:'lng', hidden:true}
];

We just added five new items to the event edit form:

  • A text field to manually type the address, an item with id:"event_location".
  • A button that users use to open Google Maps and set a point (id:"setLocation"). The item has the 'click' attribute that allows us to assign for it an 'onclick' event handler function (we named it as "setLocation").
  • Two hidden fields ('Latitude' and 'Longitude') to store the point geographic coordinates. I should mention that the mobile scheduler automatically saves event data when the user clicks the 'Save' button. The scheduler takes data for the event from the inputs defined in the edit form. That's why we added these fields but hid them, as they really don't have any value for the end users and are needed just to visualize the stored content in DB locations on Google Maps.
  • A 'notes' field (id:"details"). It's a fully optional field. We add it just to give users a possibility to add notes about upcoming events. The field has the related predefined parameter in the locale object.

So now we have an event add/edit form like this:

Mobile Event Calendar - Event Edit Form

Step 2

Before specifying the executable function for the input, we should define an event, firing which will invoke the function. The library allows us to use built-in events or any of native HTML events. We chosen the 'onFocusOut' event that occurs after an element loses focus.

To attach the event to the input, we will add the following command to the dhx.ready(function(){..} function:

dhx.event($$('scheduler').$$("event_location").$view, "focusout", setPlaceCoordinates);
  • dhx.event is a helper that attaches an event handler function for an HTML element.
  • $$('scheduler').$$("event_location") refers to the input. $view returns the view object.
  • setPlaceCoordinates() function will take an address typed by the user, detect its coordinates (to save in the DB), and display the address marker on the map.

The setPlaceCoordinates() function has the following implementation:

function setPlaceCoordinates(){
            if (marker!=null){
                var eventId = $$("scheduler").getCursor();

                var geocoder =  new google.maps.Geocoder();
                var address = $$('scheduler').$$("event_location").getValue();

                if (address !=""){
                     geocoder.geocode( { 'address': address}, function(results, status) {
                        if (status == google.maps.GeocoderStatus.OK) {
                            $$('scheduler').$$("lat").setValue(results[0].geometry.location.Xa);
                            $$('scheduler').$$("lng").setValue(results[0].geometry.location.Ya);
                        } else {
                            dhx.alert("Unfortunately,your location is not found.");
                            if ($$('scheduler').$$("lat")==""){
                                $$('scheduler').$$("lat").setValue(51.477840);
                                $$('scheduler').$$("lng").setValue(-0.001492);
                                $$('scheduler').$$("event_location").setValue("Blackheath Avenue London, Greenwich, Greater London SE10 8XJ, UK");
                            } else{
                                if (eventId!=null){
                                    var item = $$("scheduler").item(eventId);
                                    $$('scheduler').$$("event_location").setValue(item.event_location);
                                }
                            }
                        }
                     });
                }
            }
        };

Let's consider the order in which the interpreter steps through the handler code:

  • Using the $$("scheduler").getCursor() command, the interpreter gets the object of the event that the edit form is open for.
  • Then, activates the geocoding service (that converts addresses, like "Berlin, Germany", into geographic coordinates) and takes the typed address from the input (var address).

The root if-else conditional expression checks the value of the "Location" text field:

  • If the value is an empty string, searching is skipped.
  • If the user entered some value, this value is passed to the Google Maps service.
  • If some address is found, the interpreter writes its coordinates into the 'Latitude' and 'Longitude' hidden fields.
  • If the typed address doesn’t exist or the user closes the edit form before the service has finished the search, the app alerts the message informing about unsuccessful results and in the inputs keeps coordinates stored for the event in the database.
  • The if ($$('scheduler').$$("lat")==""){} else{} conditional expression is used to check whether the event in question is stored in the DB or if it’s a new event (because if the event is new, the interpreter couldn't take its coordinates from db and an error will occur). In the case the event is new and search wasn't complete, the app assigns to the event coordinates of the Greenwich Royal Observatory.

Step 3

The executable function, or an 'onClick' handler that occurs when the user clicks the 'Locate on the map' button, contains this code:

        function setLocation(){
            if (marker!=null){
                 /*shows the view of multiview*/
                $$("scheduler").$$("locationView").show();
                /*necessary to resize google map*/
                $$("scheduler").$$("locationView").resize();
                var point;
                var eventId = $$("scheduler").getCursor();

                 if (eventId!=null){
                     var item = $$("scheduler").item(eventId);
                    /*LatLng point*/
                     point = new google.maps.LatLng(item.lat,item.lng);
                    marker.title = item.event_location;
                 } else{
                    point = new google.maps.LatLng(51.477840, -0.001492); // the coordinates of the Greenwich Royal Observatory
                    marker.title = "Blackheath Avenue London, Greenwich, Greater London SE10 8XJ, UK";
            }

            var map = $$("scheduler").$$("mymap").map;
            map.setZoom(6);
            map.setCenter(point);

            marker.position= point;
            marker.map = map;
            marker.setMap(map);

            google.maps.event.addListener(map, "click", function (e) {

                    var request = {
                        location:e.latLng,
                        radius:'1'
                    };
                    service = new google.maps.places.PlacesService(map);
                    service.search(request, function(results, status){
                        if (status == google.maps.places.PlacesServiceStatus.OK) {
                            this.service.getDetails({ reference: results[0].reference }, function(details, status) {
                                if (status == google.maps.places.PlacesServiceStatus.OK) {
                                    $$('scheduler').$$("lat").setValue(details.geometry.location.Xa);
                                    $$('scheduler').$$("lng").setValue(details.geometry.location.Ya);

                                    marker.title = details.formatted_address;
                                    marker.position= e.latLng;
                                    marker.map = map;
                                    marker.setMap(map);
                                    $$('scheduler').$$("event_location").setValue(marker.title);
                                }
                            });
                        }
                    });

            });
}
    };

The lines of the code do the following:

  • The first lines show the view with Google Maps in the full screen mode and get the event object.
  • The first if-else conditional expression checks whether the user edits an existing event or if he creates a new one. This check is made in order to set the initial marker on the map.
  • If the user edits an existing event, the code generates a point with the coordinates from the DB.
  • If the user creates a new event, the code assigns the coordinates of the Greenwich Royal Observatory to it.
  • $$("scheduler").$$("mymap") refers to the 'googlemap' view. The map property returns the object of Google Maps.
  • The next lines zoom, center the map, and set the marker in the specified point.
  • google.maps.event.addListener() command attaches a function for handling clicks made by the user on the map. See the details of the used API in the Google Mas API Web Services.

One more point to mention: at this step we'll add a function to handle map clicks. But in case the user only views the location of an event, he shouldn't have a possibility to change it (step 5). So, along with adding the 'click' listener in the setLocation function, we will switch it off for the 'preview' mode (the showLocation function).

Add this command to the existing code of the showLocation() function:

function showLocation(){
         google.maps.event.clearListeners(map, "click");
    ...
};

That's the end of this tutorial! Now you can download the final demo package to see how everything works and fits together in the event calendar we've built.


Conclusion

With the increasing use of mobile phones, there is no need to say how important it is to have a mobile version of a website or app. If you need an event calendar that can be viewed and edited online on phones, then the mobile version of dhtmlxScheduler can be a huge time saver because it offers a ready-to-use calendar UI and a set of basic features. The open-source GNU GPL license allows you to use the scheduler for free on websites and internal apps.

Advertisement