Zimlet cookbook based on JavaScript API

Zimlet cookbook based on JavaScript API

   KB 2823        Last updated on 2021-07-17  




0.00
(0 votes)
Warning: You are looking at legacy Zimlet documentation. For Zimbra Modern UI Zimlet development go to: https://wiki.zimbra.com/wiki/DevelopersGuide#Zimlet_Development_Guide.

Zimlet (ZmZimletBase object)

Mail

How to get access to an email("ZmMailMsg" object) when its drag-dropped onto Zimlet's panel Item

First off we need to allow message(ZmMailMsg) or conversation(ZmConv) to be dragged onto Zimlet Panel Item.

  <zimletPanelItem label="Zimlet Name" icon="Zimlet-panelIcon">
		<dragSource type="ZmConv" />
		<dragSource type="ZmMailMsg" />
  </zimletPanelItem>

Then, within the Zimlet's Javascript file, we need to override 'doDrop' function.

com_zimbra_coloredemails.prototype.doDrop =
function(zmObject) {

};

The email dropped(zmObject) can be either a conversation or a single email. This zmObject internally has an object called 'srcObj' (source-object) and contains the real or actual Zimbra Object that was dropped. Once we get access to this, we can access all the properties and even functions.

com_zimbra_coloredemails.prototype.doDrop =
function(zmObject) {
     var msgObj = zmObject.srcObj;//get access to source-object

    //if its a conversation i.e. "ZmConv" object, get the first loaded message "ZmMailMsg" object within that.
    if (zmObject.type == "CONV") {
      msgObj  = zmObject.getFirstHotMsg();
    }

};


At this point we have a single message "ZmMailMsg" object and we can access all its properties.

Get mail subject

  var subject = msgObj.subject; //where msgObj is of type "ZmMailMsg"

Get all email address

var emailsArray =  msgObj.getEmails(); //where msgObj is of type "ZmMailMsg"

Get email addresses only in CC

var ccemails = [];//stores all email address in the email
var participants = msgObj.participants.getArray(); //where msgObj is of type "ZmMailMsg"
for(var i =0; i < participants.length; i++) {
     if(participants[i].type == AjxEmailAddress.CC) {
      ccemails.push(participants[i].address);
    }
}

 PS: Similarly you can get emails of the following types:
 AjxEmailAddress.FROM = "FROM";
 AjxEmailAddress.TO = "TO";
 AjxEmailAddress.CC = "CC";
 AjxEmailAddress.BCC = "BCC";
 AjxEmailAddress.REPLY_TO = "REPLY_TO";
 AjxEmailAddress.SENDER = "SENDER";

Get email body

var body = msgObj.getBodyContent();

In Zimbra 8.0.7 there appears not to be an getBodyContent().
var msg = zmObject.body;
The above will give the body part (for large messages it will 
only return the part that is displayed on the screen, one must 
click 'view entire message' before it returns the whole message)

Get list of attachments

var attArray =  msgObj.attachments;

//get attachments embedded in the mail body
var inlineAttachmentsArry =  msgObj.getInlineAttachments() ;
 

Get list of Tags

var tagsArray =  msgObj.tags;//returns array containing all the tags tagged to this message
 

Get List of all Emails or Conversations

The below code works when a user clicks on a panelItem when in mail app. PS: - You will have to make sure you are in mail app. - Depending on the view(Message or Conversation), the array will contain corresponding("ZmMailMsg" or "ZmConv") items

com_zimbra_test.prototype.singleClicked =
function() {
   var msgArray = appCtxt.getCurrentController().getList().getArray();
};

Download Entire Email

You can download the whole email(including all its attachments). This example is from com_zimbra_emaildownloader Zimlet.

The logic is to simply get the message's id and then do a simple http GET to http://<server>/home/message.txt?fmt=tgz&id=<messageId>
com_zimbra_emaildownloader.prototype.doDrop =
function(msgObj) {
	this.srcMsgObj = msgObj.srcObj;
	if(this.srcMsgObj.type == "CONV"){
		this.srcMsgObj = this.srcMsgObj.getFirstHotMsg();
	}
	var url = [];
	var i = 0;
	var proto = location.protocol;
	var port = Number(location.port);
	url[i++] = proto;
	url[i++] = "//";
	url[i++] = location.hostname;
	if (port && ((proto == ZmSetting.PROTO_HTTP && port != ZmSetting.HTTP_DEFAULT_PORT) 
		|| (proto == ZmSetting.PROTO_HTTPS && port != ZmSetting.HTTPS_DEFAULT_PORT))) {
		url[i++] = ":";
		url[i++] = port;
	}
	url[i++] = "/home/";
	url[i++]= AjxStringUtil.urlComponentEncode(appCtxt.getActiveAccount().name);
	url[i++] = "/message.txt?fmt=tgz&id=";
	url[i++] = this.srcMsgObj.id;

	var getUrl = url.join(""); 
	window.open(getUrl, "_blank");//do http get
};

Get base64-encoded attachment of a mail

This currently doesn't seem to be included into the main Zimbra functions. I have written a JSP and used the nice base64-encoder/decoder by Christian d'Heureuse located here:

[1]

to accomplish it. Use the jsp by calling it from your zimlet like this:

var jspUrl = this.getResource("attachmentToBase64.jsp")+"?messageUrl="+AjxStringUtil.urlComponentEncode(attachment_url);
var response = AjxRpc.invoke(null, jsp_url, null, null, true, null);
var base64 = response.text;

The attachment_url is located using <ZmMsg>.getAttchmentLinks();

File:AttachmentToBase64.tar.gz

Tags

Tag an email or entire conversation upon drop onto Zimlets


Assume that we have a tag by name "testTag" and want to tag any email or conversation when they are dropped onto the Zimlet.
 
com_zimbra_test.prototype.doDrop =
function (msgOrConvObj) {
   this.tagAction(true, msgOrConvObj.srcObj, "testTag");
}

com_zimbra_test.prototype.tagAction =
function (doTag, msgOrConvObj, tagName) {
        var tagObj = appCtxt.getActiveAccount().trees.TAG.getByName(tagName);
        if(!tagObj)
            return;
        var tagId = tagObj.id;
	var axnType = "";
	if (doTag)
		axnType = "tag"; //tag
	else
		axnType = "!tag"; //untag

	var soapCmd = ZmItem.SOAP_CMD[msgOrConvObj.type] + "Request";
	var itemActionRequest = {};
	itemActionRequest[soapCmd] = {_jsns:"urn:zimbraMail"};
	var request = itemActionRequest[soapCmd];
	var action = request.action = {};
	action.id = msgOrConvObj.id;
	action.op = axnType;
	action.tag = tagId;
	var params = {asyncMode: true, callback: null, jsonObj:itemActionRequest};
	appCtxt.getAppController().sendRequest(params);
};
 

AddressBook

Scan AddressBook

A lot of times you might want to scan the whole addressbook and get hold of all the contacts in them. You can then use that list to do a lot of batch-processing like: tagging contacts based on company, moving them based on some criteria, deleting them etc.

In theory, AjxDispatcher.run("GetContacts") should return us all the contacts but a lot of times users have 1000s and 1000s of contacts and it takes time for core-Zimbra to create that list. So, if you try to use it right away, you will end-up having only partial list of contacts. The below code-snippet, avoids that by recurrsively waiting for all the contacts to load. Once loaded, have all the contacts in this._contactList.

Since it involves ajax-calls, we need to first create a post-callback of the function(this.doSomeThingWithAllLoadedContacts) that needs these contact and pass that to this.loadAllContacts
com_zimbra_test.prototype.test = function() {
	var  postCallback = new AjxCallback(this, this._doSomeThingWithAllLoadedContacts);
	this.loadAllContacts(postCallback);
};
com_zimbra_test.prototype.loadAllContacts = function(postCallBack) {
	this.__oldNumContacts = 0;
	this._noOpLoopCnt = 0;
	this._totalWaitCnt = 0;

	if(this._contactsAreLoaded) {//2nd time onwards..
		if(postCallback) {
			postCallback.run(this);
			return;
		}
	}
	var transitions = [ ZmToast.FADE_IN, ZmToast.PAUSE,  ZmToast.PAUSE,  ZmToast.PAUSE,  ZmToast.PAUSE,  ZmToast.FADE_OUT ];
	appCtxt.getAppController().setStatusMsg("Please wait, scanning Address Book(it might take upto a minute)..", ZmStatusView.LEVEL_INFO, null, transitions);
	this._contactsAreLoaded = false;
	this._waitForContactToLoadAndProcess(postCallBack);
	this._contactsAreLoaded = true;
};
com_zimbra_test.prototype._waitForContactToLoadAndProcess = function(postCallback) {
	this._contactList = AjxDispatcher.run("GetContacts");
	if (!this._contactList)
		return;

	this.__currNumContacts = this._contactList.getArray().length;
	if (this._totalWaitCnt < 2 || this._noOpLoopCnt < 3) {//minimum 2 cycles post currentCnt==oldCnt
		if (this.__oldNumContacts == this.__currNumContact) {
			this._noOpLoopCnt++;
		}
		this._totalWaitCnt++;
		this.__oldNumContacts = this.__currNumContact;
		setTimeout(AjxCallback.simpleClosure(this._waitForContactToLoadAndProcess, this, postCallback), 5000);
	} else {//process..
		if(postCallback) {
			postCallback.run(this);
		}
	}
};

Notebook

Tasks

Briefcase

Toolbar

For convenience purposes we can add a Zimlet button on the top toolbar. We might also modify an existing button to perform differently as well. This section provides few examples on how to deal this aspect.

To start with, we need to get access to the toolbar-itself in order to access toolbar buttons. In order to do this we need to know how things are connected. In general, 'an application' has at least one 'View'-object (usually multiple-views). And each of these 'Views' have individual 'Toolbars'-objects. i.e. if we need to add a button or change a button we need to add it to all these (possibly) multiple toolbars.

For example: Mail has 4 views: Conversation view, Conversation List View, Message View & Message List View. And each one of these views has a toolbar object (i.e. total 4 toolbars). So, if we want to add a button or update a button, we need to apply it to all those views.



How to override an *existing* toolbar button in mail app (to do something else)

The below code snippet allows replacing (Junk/Not Junk)button's(ZmId.OP_SPAM) functionality with a custom functionality(newSelectionListener). PS: This is only for mail app.

com_zimbra_test1.prototype.init = function() {
	//call this immediately after login to set new listner for SPAM button
	this.replaceMailToolbarBtnListener(ZmId.OP_SPAM, new AjxListener(this, this._newSelectionListener));
};

//Replace ALL listeners that are registered for SELECTION event with the new listner
com_zimbra_test1.prototype.replaceMailToolbarBtnListener = function(btnName, newListner) {
	var controller = null;
	var btn = null;
	//keep track of all the views for which we have already set a new listner
	if (this._viewHasNewListner == undefined) {
		this._viewHasNewListner = [];
	}

	var viewId = appCtxt.getAppViewMgr().getCurrentViewId();
	if (viewId != ZmId.VIEW_CONVLIST && viewId != ZmId.VIEW_CONV && viewId != ZmId.VIEW_TRAD && viewId == ZmId.VIEW_MSG) {
		return;
	}
	if (this._viewHasNewListner[viewId]) {//already has listener
		return;
	}

	if (viewId == ZmId.VIEW_CONVLIST) {
		controller = AjxDispatcher.run("GetConvListController");
		btn = controller._toolbar.CLV.getButton(btnName);
	} else if (viewId == ZmId.VIEW_CONV) {
		controller = AjxDispatcher.run("GetConvController");
		btn = controller._toolbar.CV.getButton(btnName);
	} else if (viewId == ZmId.VIEW_TRAD) {
		controller = AjxDispatcher.run("GetTradController");
		btn = controller._toolbar.TV.getButton(btnName);
	} else if (viewId == ZmId.VIEW_MSG) {
		controller = AjxDispatcher.run("GetMsgController");
		btn = controller._toolbar.MSG.getButton(btnName);
	}
	this._viewHasNewListner[viewId] = true;
	btn.removeSelectionListeners();//remove all earlier listners
	btn.addSelectionListener(newListner);
};


//onShowView is called everytime a view is changed. It adds listners when user changes views(as they appear)
com_zimbra_test1.prototype.onShowView = function(viewId, isNewView) {
	if (viewId == ZmId.VIEW_CONVLIST || viewId == ZmId.VIEW_CONV || viewId == ZmId.VIEW_TRAD || viewId == ZmId.VIEW_MSG) {
		this.replaceMailToolbarBtnListener(ZmId.OP_SPAM, new AjxListener(this, this._newSelectionListener));
	}
};

//New listener
com_zimbra_test1.prototype._newSelectionListener = function(obj) {
	alert("Do Something Else Here!");
};

How to add additional functionality to an *existing* toolbar button

The below code snippet allows adding more functionailty to (Junk/Not Junk)aka SPAM button's existing functionality. i.e When a user clicks on Junk button, it will mark the mail as Junk AND then calls the function newSelectionListener .

PS: Please go through the section 'How to override a toolbar button in mail app (to do something else)' for the complete code. And simply comment the btn.removeSelectionListeners() in the function replaceMailToolbarBtnListener.

    Simply comment the btn.removeSelectionListeners() in the function replaceMailToolbarBtnListener .
  com_zimbra_test1.prototype.replaceMailToolbarBtnListener = function(btnName, newListner) {
       .......
       .......
	//btn.removeSelectionListeners();//comment this line
       .......
       .......
	
};
//New listener
com_zimbra_test1.prototype._newSelectionListener = function(obj) {
	alert("Do Something Else Here!");
};

How to add a Zimlet toolbar button

Verified Against: ZCS 5.0 or later Date Created: 3/31/3009
Article ID: https://wiki.zimbra.com/index.php?title=Zimlet_cookbook_based_on_JavaScript_API Date Modified: 2021-07-17



Try Zimbra

Try Zimbra Collaboration with a 60-day free trial.
Get it now »

Want to get involved?

You can contribute in the Community, Wiki, Code, or development of Zimlets.
Find out more. »

Looking for a Video?

Visit our YouTube channel to get the latest webinars, technology news, product overviews, and so much more.
Go to the YouTube channel »

Jump to: navigation, search