Page Example

Chatter Sample Overview

The Chatter sample application demonstrates how you can send and receive text messages between devices and view the message history of a specific caller.

The following figure illustrates the main screens of the Chatter.

Figure: Chatter screens

Chatter screens

The application opens with the Chatter main screen, where a caller list is displayed. To start a chat with a caller, click the caller name on the list.

On the message screen, enter a new message and click Send.

To chat with somebody who is not listed in the caller list on the main screen, click New chat on the main screen. Enter the phone number of the person you want to chat with and click Create Chat.

Prerequisites

  • 2 devices with active SIM cards
  • To ensure proper application execution, the following privileges must be set:

    • http://tizen.org/privilege/application.launch
    • http://tizen.org/privilege/contact.read
    • http://tizen.org/privilege/contact.write
    • http://tizen.org/privilege/messaging.read
    • http://tizen.org/privilege/messaging.write

Source Files

You can create and view the sample application project including the source files in the IDE.

File name Description
config.xml This file contains the application information for the platform to install and launch the application, including the view mode and the icon to be used in the device menu.
index.html This is a starting file from which the application starts loading. It contains the layout of the application screens.
css/style.css This file contains CSS rules for the application UI.
js/app.helpers.js This file contains the helper functions used by the application.
js/app.js This file defines the main application class
js/app.model.js This file handles the data used by the common part of the application.
js/app.ui.events.js This file handles the user interface events.
js/app.ui.js This file handles the application UI.
js/app.ui.templateManager.js This file contains the TemplateManager class that manages, caches, and provides HTML templates.
js/main.js This file starts the application.
lib/tau/ This directory contains the external libraries (TAU library).

Implementation

On application launch during the creation of the model module, the Contact API is used to get contacts from the phone address book. The returned result is a unified address book object.

/* app.model.js */
function Model() 
{
   'use strict';

   /* Unified addressbook object */
   this.addressBook = tizen.contact.getUnifiedAddressBook();

   /* (…) */
}

After that, the SMS service is initialized.

/* app.model.js */
init: function Model_init() 
{
   this.loadContacts();
   this.initSmsService();
},

initSmsService: function Model_initSmsService() 
{
   var self = this;
   try 
   {
      tizen.messaging.getMessageServices('messaging.sms', function onMessageServicesFound(s) 
      {
         self.smsService = s[0];
         /* Prepare the messages */
         self.prepareMessages(app.fillUpMessagePage.bind(app));
         /* Add the change listener */
         self.messagesChangeListener();
      });
   } 
   catch (error) 
   {
      console.error(error.message);
   }
},

On success available messages are prepared and message change listener is added.

An event listener detects whether messages were changed, added, or removed using the addMessagesChangeListener() method of the MessageStorage interface. When messages are added or removed, the prepareMessages() method is called to refresh the message list.

In the prepareMessage() method the messages are retrieved using the findMessages() method of the MessageStorage interface. The first argument of the method is the AttributeFilter filter object of the Tizen API, and the second argument is an event handler for sorting the messages (the newest on top). The retrieved messages are saved in the messagesList variable and sorted by date.

/* app.model.js */
messagesChangeListener: function Model_initSmsService() 
{
   var self = this,
   messageChangeCallback = 
   {
      messagesupdated: function onMessagesUpdated(messages) 
      {
         if (messages[0].messageStatus !== 'SENDING') 
         {
            app.ui.changeMessageStatus(messages[0]);
         }
      },
      messagesadded: function onMessagesAdded() 
      {
         self.prepareMessages(app.ui.showMessageChat);
      },
      messagesremoved: function onMessagesRemoved() 
      {
         self.prepareMessages(app.ui.showMessageChat);
      }
   };
   this.smsService.messageStorage.addMessagesChangeListener(messageChangeCallback);
},

prepareMessages: function Model_prepareMessages(callback) 
{
   var self = this;
   try 
   {
      this.smsService.messageStorage.findMessages(new tizen.AttributeFilter('type', 'EXACTLY', 'messaging.sms'),
      function onMessagesLoaded(messages) 
      {
         function compare(a, b) 
         {
            if (a.timestamp > b.timestamp) 
            {
               return -1;
            }
            if (a.timestamp < b.timestamp) 
            {
               return 1;
            }

            return 0;
         }
         messages.sort(compare);
         self.messagesList = self.groupMessages(messages);
         app.ui.loadCallerList();
         callback();
      },
      function onError() 
      {
         console.error('prepareMessage: error');
      });
   } 
   catch (err) 
   {
      console.error(err);
   }
},

Messages in messagesList are grouped according to their recipient key. Each recipient object consists of a message array and the latest message. The grouped object can be used for both main and chat screens of the Chatter application.

/* app.model.js */
groupMessages: function Model_groupMessages(messages) 
{
   var i, obj = {}, folderId;
   for (i in messages) 
   {
      if (messages.hasOwnProperty(i)) 
      {
         folderId = messages[i].folderId;
         if ((folderId !== null &&
         folderId !== this.DRAFTS_FOLDER) ||
         messages[i].messageStatus === 'DRAFT') 
         {
            if (messages.hasOwnProperty(i)) 
            {
               obj = this.groupMessagesSingle(messages[i], obj);
            }
         }
      }
   }

   return obj;
},

groupMessagesSingle: function Model_groupMessagesSingle(message, obj) 
{
   var key, j;
   if (message.from) 
   {
      key = message.from;
      obj[key] = this.pushData(message, obj[key]);
   } else 
   {
      for (j in message.to) 
      {
         if (message.to.hasOwnProperty(j)) 
         {
            key = message.to[j];
            obj[key] = this.pushData(message, obj[key]);
         }
      }
   }

   return obj;
},

The grouped messages are displayed as a list on the main page of the application.

After the application starts, there are three ways to start a chat:

  • By tapping on the list of started conversations from the list on the main page.
    /* app.ui.js */
    onCallerListElementTap: function Ui_onCallerTap(event, element) 
    {
       event.preventDefault();
       event.stopPropagation();
       app.setCurrentNumber(element.attr('phone'));
       app.setCurrentCaller(element.attr('caller'));
       $.mobile.changePage('#chat');
    },
    
    /* app.ui.events.js */
    $('#chat, #main').on('pageshow', function onMessagePageShow() 
    {
       app.fillUpMessagePage();
    });
    

    The app.fillUpMessagePage()method is shown below.

  • By tapping the New chat button on the bottom of the screen, and then tapping on:
    • A contact from the sorted list based on model.addressBook.
      /* app.js */
      showContactsLoaded: function App_showContactsLoaded() 
      {
         var i, len, sortedContactList = [];
      
         if (this.model.contactsLoaded !== null &&
         this.model.contactsLoaded.length) 
         {
            len = this.model.contactsLoaded.length;
            for (i = 0; i < len; i += 1) 
            {
               if (this.model.contactsLoaded[i].phoneNumbers.length) 
               {
                  sortedContactList.push(
                  {
                     caller: this.helpers.getCallerName(this.model.contactsLoaded[i], 'no name'),
                     number: this.model.contactsLoaded[i].primaryNumber,
                     contact: this.model.contactsLoaded[i]
                  });
               }
            }
      
            sortedContactList.sort(function compareContacts(a, b) 
            {
               if (a.caller < b.caller) 
               {
                  return -1;
               }
               if (a.caller > b.caller) 
               {
                  return 1;
               }
      
               return 0;
            });
         }
         this.ui.fillContactList(sortedContactList);
      },
      
    • The Enter number button, and then entering and accepting the phone number.

When the application opens the chat page, the page is filled with messages content.

/* app.js */
fillUpMessagePage: function App_fillUpMessagePage() 
{
   var activePageId = $.mobile.activePage.attr('id');
   if (activePageId === 'main') 
   {
      this.ui.loadCallerList();
   } 
   else if (activePageId === 'contactSelect') 
   {
      this.showContactsLoaded();
   } 
   else 
   {
      this.ui.showMessageChat();
   }
},

If there are messages, the message bubbles are shown.

/* app.ui.js */
showMessageChat: function Ui_showMessageChat() 
{
   var ul, li, i, date, data,
   key = app.getCurrentNumber(), messages;
   if (document.webkitVisibilityState === 'visible' &&
   $.mobile.activePage.attr('id') === 'chat') 
   {
      /* Update name if changed */
      /* Set the caller phone number */
      app.setCurrentCaller(app.model.getNameByNumber(key));
      $('#chat-title').text(app.getCurrentCaller());
      ul = $('#message-chat').empty();

      if (app.model.messagesList[key] !== undefined) 
      {
         /* Get messages from prepared messages */  
         messages = app.model.messagesList[key].messages;
         i = messages.length;
         while (i--) 
         {
            date = new Date(messages[i].timestamp);
            data = 
            {
               'bubbleType': messages[i]
               .to.length === 0 ? 'left' : 'right',
               'label': app
               .model.getNameByNumber(messages[i].from),
               'caller': messages[i].caller,
               'id': messages[i].id,
               'plainBody': messages[i].body.plainBody,
               'date': app.helpers.getDate(date),
               'hour': date.getHours(),
               'minutes': app
                  .helpers.addZeroBefore(date.getMinutes()),
               'status': messages[i].messageStatus
            };

            if (!messages[i].isRead) 
            {
               app.model.setRead(messages[i]);
            }

            li = $(app.ui.templateManager
            .get('normalBubble', data));
            if (messages[i].messageStatus === 'DRAFT') 
            {
               li.one('click', app.model.editDraft
               .bind(app.model, messages[i], li));
            }
            ul.append(li);
         }
         ul.listview('refresh');
         app.ui.scrollToBottom();
      }
   }
},

After the user writes the message and taps the Send button, the sendMessage() method is called to send the message using the smsService object. The message type and recipient information are included as arguments for the method.

The message text is taken from the DOM element. The phone number was stored in a variable in the app module when the chat page was displayed (in the showMessageChat() method of the UI class).

 
/* app.ui.events.js */
$('#send').on('vclick', function onSendClick(event) 
{
   var text = $('#text').blur().val();
   event.stopPropagation();
   self.ui.resetTextAreas();
   self.ui.setChatCounterValue();
   self.ui.checkChatSendButtonState();
   /* Send message */
   app.sendMessage(text, [app.getCurrentNumber()]);
});
/* app.js */
getCurrentNumber: function App_getCurrentNumber() 
{
   return this.currentNumber;
},

sendMessage: function App_sendMessage(text, numbers) 
{
   var onError = function onError(e) 
   {
      var message = 'Unknown error.';
      if (e) 
      {
         if (e.type === 'NetworkError') 
         {
            message = 'Network error.';
         } 
         else if (e.type === '') 
         {
            message = 'Invalid number.';
         }
      }
      app.ui.showSendErrorPopup(message);
   };
   /* Send message */
   this.model.sendMessage(numbers, text, 
                          app.model.prepareMessages.bind(app.model, app.ui.showMessageChat), onError);
}
/* app.model.js */
sendMessage: function Model_sendMessage(num, text, onSuc, onErr) 
{
   var message;
   onSuc = onSuc || function noop() 
   { 
      return; 
   };
   onErr = onErr || function noop() 
   { 
      return; 
   };

   /* Send a message to specified number with entered text */
   message = new tizen.Message('messaging.sms', {plainBody: text, to: [num]});
   try 
   {
      this.smsService.sendMessage(message, onSuc, function onError(e) 
      {
         onSuc();
         console.error(
         'Could not send the message. Error: ' +
         e.name + ': ' +  e.message, e);
         onErr(e);
      });
   } 
   catch (e) 
   {
      onSuc();
      console.error(
      'Could not send the message. Exception: ' +
      e.name + ':' + e.message, e);
      onErr(e);
   }
},

The Application API is used to exit the application in the exit method of the App module.

/* app.js */
exit: function App_exit() 
{
   var application = tizen.application.getCurrentApplication();
   application.exit();
},