WebApp: Removing unwanted recipient suggestions

December 14, 2012 by bhavin   Comments (0)

, ,

 

Every time user sends an email, WebApp maintains history of the used email addresses for suggestion purposes. All collected addresses are available as suggestions while the user is trying to fill address fields like “TO” and “CC” while creating, replying to or forwarding an email.

image

 
Starting from version 1.3.svn38734, WebApp has the option to delete any address from the suggestion list. When the suggestion list appears, the user can select any single address by using the up and down arrow keys. After selecting an item, you can delete that particular address by just pressing the delete key on the keyboard.
 
Additionally, if there are two contacts that have the same email address but a different name than the existing suggestion list entry, it will be overwritten by the new entry based on sent priority, and only a single item is stored in suggestion list history.
 

image

 
There is a class Zarafa.common.recipientfield.ui.RecipientField that extends Zarafa.common.ui.BoxField, which is responsible for generating and displaying the suggestion list.
 
In the above mentioned class all the suggestion data will be loaded/provided by a store named Zarafa.common.recipientfield.data.SuggestionListStore that extends Ext.data.Store, which has the following configuration options.
 
Ext.applyIf(config, {
    batch: true,
    autoSave: true,
    remoteSort: false,
    actionType : Zarafa.core.Actions['list'],
    proxy: new Zarafa.common.recipientfield.data.SuggestionListProxy(),
    writer: new Zarafa.common.recipientfield.data.SuggestionListJsonWriter(),
    reader: new Ext.data.JsonReader({
        root: 'result',
        id : 'id'
    }, Zarafa.common.recipientfield.data.SuggestionListRecord)
});
 
The class Zarafa.common.recipientfield.data.SuggestionListProxy, which extends Zarafa.core.data.MAPIProxy is responsible for communication with the server. That means it is sending the request from client to server and handling the response coming from server to client. There is a config-option defining the module name which handles this particular request on server side. This proxy class also has a method named getResponseHandlerForRequest() which will create an object of class Zarafa.common.recipientfield.data.SuggestionListResponseHandler.
 
SuggestionListResponseHandler class extends Zarafa.core.data.ProxyResponseHandler and is responsible for handling responses arriving from the server side.
 
Zarafa.common.recipientfield.data.SuggestionListJsonWriter() extends Ext.data.JsonWriter and it prepares data to send to server. Here, destroyRecord() method is overriden to explicitly call toHash() method to prepare all properties of recipient record as data. This additional writer class is required as we need to send all recipient record properties to the server instead of only ID of that record at the time of delete request.
 
Ext.data.JsonReader reads the data coming from server side and makes records from that data by using record properties defined in Zarafa.common.recipientfield.data.SuggestionListRecord.
Delete key is handled by Ext.KeyNav which provides a convenient way to bind keyboard keys to functions that will get called when the keys are pressed.

The delete key handling is present in Zarafa.common.ui.BoxField class which extends Ext.form.ComboBox.

The following code is used in the initEvents() method to listen to Delete key press events and bind onKeyNavDelete() method which removes the record from the suggestion list :

this.keyNav.del = this.onKeyNavDelete;

Method which remove selected record from suggestion list store :

onKeyNavDelete : function()
{
    this.getStore().removeAt(this.selectedIndex);
    this.restrictHeight();
}

Above method gets the store instance and fires removeAt() method, which removes the record at the specified index.
save() method of the store is called as autoSave config is set to true. Store will send the delete request if deletion is detected by save() method. Delete request will be sent to server along with necessary data as per “writer” specification.

restrictHeight() method is required to resize dropdown list.

Now we are very well familiar with the client side mechanism, and are able to send delete request with necessary data set. This data contain record properties as per Zarafa.common.recipientfield.data.SuggestionListRecord.

When such request arrives at the server side, it will be handled by the specified module. Here, module/class name is suggestemailaddressmodule.

Delete request is identified on the basis of actionType specified in request and deleteRecipient() method is called. This method is used to delete a recipient entry from already stored recipient history in mapi property. It compares email_address or smtp_address  of action array(contains data arriving from the client side) with entries of already stored recipient history.

function deleteRecipient($action, $recipient_history) {
    if(isset($action)  && !empty($recipient_history) && !empty($recipient_history['recipients'])){
    /**
    * A foreach is used instead of a normal for-loop to
    * prevent the loop from finishing before the end of
    * the array, because of the unsetting of elements 
    * in that array.
    **/
    foreach($recipient_history['recipients'] as $index => $recipient){
        if($action['email_address'] == $recipient['email_address'] || $action['smtp_address'] == $recipient['smtp_address']){
            unset($recipient_history['recipients'][$index]);
        }
    }

    // Re-indexing recipients' array to adjust index of deleted recipients
    $recipient_history['recipients'] = array_values($recipient_history['recipients']);

    // Write new recipient history to property
    $l_sNewRecipientHistoryJSON = JSON::Encode($recipient_history);
    $stream = mapi_openproperty($GLOBALS["mapisession"]->getDefaultMessageStore(), PR_EC_RECIPIENT_HISTORY_JSON, IID_IStream, 0, MAPI_CREATE | MAPI_MODIFY);
    mapi_stream_setsize($stream, strlen($l_sNewRecipientHistoryJSON));
    mapi_stream_write($stream, $l_sNewRecipientHistoryJSON);
    mapi_stream_commit($stream);
    mapi_savechanges($GLOBALS["mapisession"]->getDefaultMessageStore());
}

// send success message to client
$this->sendFeedback(true);

The recipient history is re-indexed after removing an entry to maintain proper indexing.

Links:
WebApp API documentation: http://community.zarafa.com/webapp/