1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc.
  5  *
  6  * The contents of this file are subject to the Common Public Attribution License Version 1.0 (the "License");
  7  * you may not use this file except in compliance with the License.
  8  * You may obtain a copy of the License at: https://www.zimbra.com/license
  9  * The License is based on the Mozilla Public License Version 1.1 but Sections 14 and 15
 10  * have been added to cover use of software over a computer network and provide for limited attribution
 11  * for the Original Developer. In addition, Exhibit A has been modified to be consistent with Exhibit B.
 12  *
 13  * Software distributed under the License is distributed on an "AS IS" basis,
 14  * WITHOUT WARRANTY OF ANY KIND, either express or implied.
 15  * See the License for the specific language governing rights and limitations under the License.
 16  * The Original Code is Zimbra Open Source Web Client.
 17  * The Initial Developer of the Original Code is Zimbra, Inc.  All rights to the Original Code were
 18  * transferred by Zimbra, Inc. to Synacor, Inc. on September 14, 2015.
 19  *
 20  * All portions of the code are Copyright (C) 2006, 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * @overview
 26  * This file contains the contact group view classes.
 27  */
 28 
 29 /**
 30  * Creates the group view.
 31  * @class
 32  * This class represents the contact group view.
 33  * 
 34  * @param	{DwtComposite}	parent		the parent
 35  * @param	{ZmContactController}		controller		the controller
 36  *
 37  * @constructor
 38  * 
 39  * @extends		DwtComposite
 40  */
 41 ZmGroupView = function(parent, controller) {
 42 	if (arguments.length == 0) return;
 43 	DwtComposite.call(this, {parent:parent, className:"ZmContactView", posStyle:DwtControl.ABSOLUTE_STYLE});
 44 	this.setScrollStyle(Dwt.CLIP); //default is clip, for regular group it's fine. (otherwise there's always a scroll for no reason, not sure why). For DL we change in "set"
 45 
 46 	this._controller = controller;
 47 
 48 	this._view = ZmId.VIEW_GROUP;
 49 
 50 	this._tagList = appCtxt.getTagTree();
 51 	this._tagList.addChangeListener(new AjxListener(this, this._tagChangeListener));
 52 
 53 	this._changeListener = new AjxListener(this, this._groupChangeListener);
 54 	this._detailedSearch = appCtxt.get(ZmSetting.DETAILED_CONTACT_SEARCH_ENABLED);
 55 
 56 	/* following few are used in ZmContactPicker methods we delegate to from here */
 57 	this._ascending = true; 
 58 	this._emailList = new AjxVector();
 59 	this._includeContactsWithNoEmail = true;
 60 
 61 	this._groupMemberMods = {};
 62 	this._tabGroup = new DwtTabGroup(this._htmlElId);
 63 	
 64 };
 65 
 66 ZmGroupView.prototype = new DwtComposite;
 67 ZmGroupView.prototype.constructor = ZmGroupView;
 68 ZmGroupView.prototype.isZmGroupView = true;
 69 
 70 
 71 /**
 72  * Returns a string representation of the object.
 73  * 
 74  * @return		{String}		a string representation of the object
 75  */
 76 ZmGroupView.prototype.toString =
 77 function() {
 78 	return "ZmGroupView";
 79 };
 80 
 81 ZmGroupView.DIALOG_X = 50;
 82 ZmGroupView.DIALOG_Y = 100;
 83 
 84 ZmGroupView.MAIL_POLICY_ANYONE = "ANYONE";
 85 ZmGroupView.MAIL_POLICY_MEMBERS = "MEMBERS";
 86 ZmGroupView.MAIL_POLICY_INTERNAL = "INTERNAL";
 87 ZmGroupView.MAIL_POLICY_SPECIFIC = "SPECIFIC";
 88 
 89 ZmGroupView.GRANTEE_TYPE_USER = "usr";
 90 ZmGroupView.GRANTEE_TYPE_GUEST = "gst"; // an external user. This is returned by GetDistributionListResponse for a non-internal user. Could be a mix of this and "usr"
 91 ZmGroupView.GRANTEE_TYPE_EMAIL = "email"; //this covers both guest and user when setting rights via the setRights op of DistributionListActionRequest
 92 ZmGroupView.GRANTEE_TYPE_GROUP = "grp";
 93 ZmGroupView.GRANTEE_TYPE_ALL = "all";
 94 ZmGroupView.GRANTEE_TYPE_PUBLIC = "pub";
 95 
 96 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP = [];
 97 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_USER] = ZmGroupView.MAIL_POLICY_SPECIFIC;
 98 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_GUEST] = ZmGroupView.MAIL_POLICY_SPECIFIC;
 99 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_GROUP] = ZmGroupView.MAIL_POLICY_MEMBERS;
100 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_ALL] = ZmGroupView.MAIL_POLICY_INTERNAL;
101 ZmGroupView.GRANTEE_TYPE_TO_MAIL_POLICY_MAP[ZmGroupView.GRANTEE_TYPE_PUBLIC] = ZmGroupView.MAIL_POLICY_ANYONE;
102 
103 //
104 // Public methods
105 //
106 
107 // need this since contact view now derives from list controller
108 ZmGroupView.prototype.getList = function() { return null; }
109 
110 /**
111  * Gets the contact.
112  * 
113  * @return	{ZmContact}	the contact
114  */
115 ZmGroupView.prototype.getContact =
116 function() {
117 	return this._contact;
118 };
119 
120 /**
121  * Gets the controller.
122  * 
123  * @return	{ZmContactController}	the controller
124  */
125 ZmGroupView.prototype.getController =
126 function() {
127 	return this._controller;
128 };
129 
130 // Following two overrides are a hack to allow this view to pretend it's a list view
131 ZmGroupView.prototype.getSelection = function() {
132 	return this.getContact();
133 };
134 
135 ZmGroupView.prototype.getSelectionCount = function() {
136 	return 1;
137 };
138 
139 ZmGroupView.prototype.isDistributionList =
140 function() {
141 	return this._contact.isDistributionList();
142 };
143 
144 ZmGroupView.prototype.set =
145 function(contact, isDirty) {
146 	this._attr = {};
147 
148 	if (this._contact) {
149 		this._contact.removeChangeListener(this._changeListener);
150 	}
151 	contact.addChangeListener(this._changeListener);
152 	this._contact = this._item = contact;
153 
154 	if (!this._htmlInitialized) {
155 		this._createHtml();
156 		this._addWidgets();
157 		this._installKeyHandlers();
158 		this._tabGroup.addMember(this._getTabGroupMembers());
159 	}
160 	
161 	this._setFields();
162 	this._emailListOffset = 0;
163 	this._isDirty = isDirty;
164 
165 	if (contact.isDistributionList()) {
166 		if (this._usernameEditable) {
167 			this._groupNameInput.addListener(DwtEvent.ONBLUR, this._controller.updateTabTitle.bind(this._controller));
168 		}
169 		if (this._domainEditable) {
170 			document.getElementById(this._groupNameDomainId).onblur = this._controller.updateTabTitle.bind(this._controller);
171 		}
172 	}
173 	else {
174 		this._groupNameInput.addListener(DwtEvent.ONBLUR, this._controller.updateTabTitle.bind(this._controller));
175 	}
176 
177 	this.search(null, null, true);
178 };
179 
180 /**
181  * this is called from ZmContactController.prototype._postShowCallback
182  */
183 ZmGroupView.prototype.postShow =
184 function() {
185 	if (this._contact.isDistributionList()) {
186 		this._dlMembersTabView.showMe(); //have to call it now so it's sized correctly.
187 	}
188 };
189 
190 ZmGroupView.prototype.getModifiedAttrs =
191 function() {
192 	if (!this.isDirty()) return null;
193 
194 	var mods = this._attr = [];
195 
196 	// get field values
197 	var groupName = this._getGroupName();
198 	var folderId = this._getFolderId();
199 
200 	if (this.isDistributionList()) {
201 		var dlInfo = this._contact.dlInfo;
202 		if (groupName != this._contact.getEmail()) {
203 			mods[ZmContact.F_email] = groupName;
204 		}
205 		if (dlInfo.displayName != this._getDlDisplayName()) {
206 			mods[ZmContact.F_dlDisplayName] = this._getDlDisplayName();
207 		}
208 		if (dlInfo.description != this._getDlDesc()) {
209 			mods[ZmContact.F_dlDesc] = this._getDlDesc();
210 		}
211 		if (dlInfo.hideInGal != this._getDlHideInGal()) {
212 			mods[ZmContact.F_dlHideInGal] = this._getDlHideInGal() ? "TRUE" : "FALSE";
213 		}
214 		if (dlInfo.notes != this._getDlNotes()) {
215 			mods[ZmContact.F_dlNotes] = this._getDlNotes();
216 		}
217 		if (dlInfo.subscriptionPolicy != this._getDlSubscriptionPolicy()) {
218 			mods[ZmContact.F_dlSubscriptionPolicy] = this._getDlSubscriptionPolicy();
219 		}
220 		if (dlInfo.unsubscriptionPolicy != this._getDlUnsubscriptionPolicy()) {
221 			mods[ZmContact.F_dlUnsubscriptionPolicy] = this._getDlUnsubscriptionPolicy();
222 		}
223 		if (!AjxUtil.arrayCompare(dlInfo.owners, this._getDlOwners())) {
224 			mods[ZmContact.F_dlListOwners] = this._getDlOwners();
225 		}
226 		if (dlInfo.mailPolicy != this._getDlMailPolicy()
227 				|| (this._getDlMailPolicy() == ZmGroupView.MAIL_POLICY_SPECIFIC
228 					&& !AjxUtil.arrayCompare(dlInfo.mailPolicySpecificMailers, this._getDlSpecificMailers()))) {
229 			mods[ZmContact.F_dlMailPolicy] = this._getDlMailPolicy();
230 			mods[ZmContact.F_dlMailPolicySpecificMailers] = this._getDlSpecificMailers();
231 		}
232 
233 		if (this._groupMemberMods) {
234 			mods[ZmContact.F_groups] = this._getModifiedMembers();
235 			this._groupMemberMods = {}; //empty the mods
236 		}
237 
238 		return mods;
239 	}
240 
241 	// creating new contact (possibly some fields - but not ID - prepopulated)
242 	if (this._contact.id == null || (this._contact.isGal && !this.isDistributionList())) {
243 		mods[ZmContact.F_folderId] = folderId;
244 		mods[ZmContact.F_fileAs] = ZmContact.computeCustomFileAs(groupName);
245 		mods[ZmContact.F_nickname] = groupName;
246 		mods[ZmContact.F_groups] = this._getGroupMembers();
247 		mods[ZmContact.F_type] = "group";
248 	}
249 	else {
250 		// modifying existing contact
251 		if (!this.isDistributionList() && this._contact.getFileAs() != groupName) {
252 			mods[ZmContact.F_fileAs] = ZmContact.computeCustomFileAs(groupName);
253 			mods[ZmContact.F_nickname] = groupName;
254 		}
255 
256 		if (this._groupMemberMods) {
257 			mods[ZmContact.F_groups] = this._getModifiedMembers();
258 			this._groupMemberMods = {}; //empty the mods
259 		} 
260 		
261 		var oldFolderId = this._contact.addrbook ? this._contact.addrbook.id : ZmFolder.ID_CONTACTS;
262 		if (folderId != oldFolderId) {
263 			mods[ZmContact.F_folderId] = folderId;
264 		}
265 	}
266 
267 	return mods;
268 };
269 
270 ZmGroupView.prototype._getModifiedMembers =
271 function() {
272 	var modifiedMembers = [];
273 	for (var id in this._groupMemberMods) {
274 		if (this._groupMemberMods[id].op) {
275 			modifiedMembers.push(this._groupMemberMods[id]);
276 		}
277 	}
278 	return modifiedMembers;
279 };
280 
281 
282 ZmGroupView.prototype._getFullName =
283 function() {
284 	return this._getGroupName();
285 };
286 
287 ZmGroupView.prototype._getGroupDomainName =
288 function() {
289 	if (this.isDistributionList()) {
290 		return this._domainEditable
291 			? AjxStringUtil.trim(document.getElementById(this._groupNameDomainId).value)
292 			: this._emailDomain;
293 	}
294 	return AjxStringUtil.trim(document.getElementById(this._groupNameDomainId).value);
295 };
296 
297 ZmGroupView.prototype._getGroupName =
298 function() {
299 	if (this.isDistributionList()) {
300 		var username = this._getDlAddressLocalPart();
301 		return username + "@" + this._getGroupDomainName();
302 	}
303 	return AjxStringUtil.trim(this._groupNameInput.getValue());
304 };
305 
306 ZmGroupView.prototype._getDlAddressLocalPart =
307 function() {
308 	return this._usernameEditable ? AjxStringUtil.trim(this._groupNameInput.getValue()) : this._emailUsername;
309 };
310 
311 ZmGroupView.prototype._getDlDisplayName =
312 function() {
313 	return AjxStringUtil.trim(document.getElementById(this._dlDisplayNameId).value);
314 };
315 
316 ZmGroupView.prototype._getDlDesc =
317 function() {
318 	return AjxStringUtil.trim(document.getElementById(this._dlDescId).value);
319 };
320 
321 ZmGroupView.prototype._getDlNotes =
322 function() {
323 	return AjxStringUtil.trim(document.getElementById(this._dlNotesId).value);
324 };
325 
326 ZmGroupView.prototype._getDlHideInGal =
327 function() {
328 	return document.getElementById(this._dlHideInGalId).checked;
329 };
330 
331 ZmGroupView.prototype._getDlSpecificMailers =
332 function() {
333 	return this._getUserList(this._dlListSpecificMailersId);
334 };
335 
336 ZmGroupView.prototype._getDlOwners =
337 function() {
338 	return this._getUserList(this._dlListOwnersId);
339 };
340 
341 ZmGroupView.prototype._getUserList =
342 function(fldId) {
343 	var users = AjxStringUtil.trim(document.getElementById(fldId).value).split(";");
344 	var retUsers = [];
345 	for (var i = 0; i < users.length; i++) {
346 		var user = AjxStringUtil.trim(users[i]);
347 		if (user != "") {
348 			retUsers.push(user);
349 		}
350 	}
351 	return retUsers;
352 };
353 
354 
355 ZmGroupView.prototype._getDlSubscriptionPolicy =
356 function() {
357 	return this._getDlPolicy(this._dlSubscriptionPolicyId, this._subsPolicyOpts);
358 };
359 
360 ZmGroupView.prototype._getDlUnsubscriptionPolicy =
361 function() {
362 	return this._getDlPolicy(this._dlUnsubscriptionPolicyId, this._subsPolicyOpts);
363 };
364 
365 ZmGroupView.prototype._getDlMailPolicy =
366 function() {
367 	return this._getDlPolicy(this._dlMailPolicyId, this._mailPolicyOpts);
368 };
369 
370 ZmGroupView.prototype._getDlPolicy =
371 function(fldId, opts) {
372 	for (var i = 0; i < opts.length; i++) {
373 		var opt = opts[i];
374 		if (document.getElementById(fldId[opt]).checked) {
375 			return opt;
376 		}
377 	}
378 };
379 
380 
381 ZmGroupView.prototype.isEmpty =
382 function(checkEither) {
383 	var groupName = this._getGroupName();
384 	var members = ( this._groupMembersListView.getList() && this._groupMembersListView.getList().size() > 0 );
385 
386 	return checkEither
387 		? (groupName == "" || !members )
388 		: (groupName == "" && !members );
389 };
390 
391 
392 ZmGroupView.prototype.isValidDlName =
393 function() {
394 	if (!this.isDistributionList()) {
395 		return true;
396 	}
397 	if (!this._usernameEditable) {
398 		return true; //to be on the safe and clear side. no need to check.
399 	}
400 	var account = this._getDlAddressLocalPart();
401 	return AjxEmailAddress.accountPat.test(account);
402 };
403 
404 ZmGroupView.prototype.isValidDlDomainName =
405 function() {
406 	if (!this.isDistributionList()) {
407 		return true;
408 	}
409 	if (!this._domainEditable) {
410 		return true; //this takes care of a "vanilla" owner with no create rights.
411 	}
412 
413 	var domain = this._getGroupDomainName();
414 	return this._allowedDomains[domain];
415 };
416 
417 ZmGroupView.prototype.isValidOwners =
418 function() {
419 	if (!this.isDistributionList()) {
420 		return true;
421 	}
422 	return this._getDlOwners().length > 0
423 };
424 
425 
426 ZmGroupView.prototype.isValidMailPolicy =
427 function() {
428 	if (!this.isDistributionList()) {
429 		return true;
430 	}
431 	return this._getDlMailPolicy() != ZmGroupView.MAIL_POLICY_SPECIFIC || this._getDlSpecificMailers().length > 0;
432 };
433 
434 
435 
436 ZmGroupView.prototype.isValid =
437 function() {
438 	// check for required group name
439 	if (this.isDirty() && this.isEmpty(true)) {
440 		return false;
441 	}
442 	if (!this.isValidDlName()) {
443 		return false;
444 	}
445 	if (!this.isValidDlDomainName()) {
446 		return false;
447 	}
448 	if (!this.isValidOwners()) {
449 		return false;
450 	}
451 	if (!this.isValidMailPolicy()) {
452 		return false;
453 	}
454 	return true;
455 };
456 
457 //todo - really not sure why this way of having 3 methods with parallel values conditions is used here this way. I just continued to build on what was there, but should check if it can be simplified.
458 ZmGroupView.prototype.getInvalidItems =
459 function() {
460 	if (this.isValid()) {
461 		return [];
462 	}
463 	var items = [];
464 	if (!this.isValidDlName()) {
465 		items.push("dlName");
466 	}
467 	if (this.isEmpty(true)) {
468 		items.push("members");
469 	}
470 	if (!this.isValidDlDomainName()) {
471 		items.push("dlDomainName");
472 	}
473 	if (!this.isValidOwners()) {
474 		items.push("owners");
475 	}
476 	if (!this.isValidMailPolicy()) {
477 		items.push("mailPolicy");
478 	}
479 	return items;
480 };
481 
482 ZmGroupView.prototype.getErrorMessage = function(id) {
483 	if (this.isValid()) {
484 		return null;
485 	}
486 	if (id == "members") {
487 		return this.isDistributionList() ? ZmMsg.errorMissingDlMembers : ZmMsg.errorMissingGroup;
488 	}
489 	if (id == "dlName") { 
490 		return ZmMsg.dlInvalidName; 
491 	}
492 	if (id == "dlDomainName") {
493 		return ZmMsg.dlInvalidDomainName; 
494 	}
495 	if (id == "owners") {
496 		return ZmMsg.dlInvalidOwners; 
497 	}
498 	if (id == "mailPolicy") {
499 		return ZmMsg.dlInvalidMailPolicy;
500 	}
501 
502 };
503 
504 ZmGroupView.prototype.enableInputs =
505 function(bEnable) {
506 	if (this.isDistributionList()) {
507 		if (this._usernameEditable) {
508 			document.getElementById(this._groupNameId).disabled = !bEnable;
509 		}
510 		if (this._domainEditable) {
511 			document.getElementById(this._groupNameDomainId).disabled = !bEnable;
512 		}
513 		document.getElementById(this._dlDisplayNameId).disabled = !bEnable;
514 		document.getElementById(this._dlDescId).disabled = !bEnable;
515 		document.getElementById(this._dlHideInGalId).disabled = !bEnable;
516 		document.getElementById(this._dlNotesId).disabled = !bEnable;
517 		for (var i = 0; i < this._subsPolicyOpts.length; i++) {
518 			var opt = this._subsPolicyOpts[i];
519 			document.getElementById(this._dlSubscriptionPolicyId[opt]).disabled = !bEnable;
520 			document.getElementById(this._dlUnsubscriptionPolicyId[opt]).disabled = !bEnable;
521 		}
522 		document.getElementById(this._dlListOwnersId).disabled = !bEnable;
523 	}
524 	else {
525 		document.getElementById(this._groupNameId).disabled = !bEnable;
526 	}
527 	if (!this._noManualEntry) {
528 		this._groupMembers.disabled = !bEnable;
529 	}
530 	for (var fieldId in this._searchField) {
531 		this._searchField[fieldId].disabled = !bEnable;
532 	}
533 };
534 
535 ZmGroupView.prototype.isDirty =
536 function() {
537 	return this._isDirty;
538 };
539 
540 ZmGroupView.prototype.getTitle =
541 function() {
542 	return [ZmMsg.zimbraTitle, this.isDistributionList() ? ZmMsg.distributionList : ZmMsg.group].join(": ");
543 };
544 
545 ZmGroupView.prototype.setSize =
546 function(width, height) {
547 	// overloaded since base class calls sizeChildren which we dont care about
548 	DwtComposite.prototype.setSize.call(this, width, height);
549 };
550 
551 ZmGroupView.prototype.setBounds =
552 function(x, y, width, height) {
553 	DwtComposite.prototype.setBounds.call(this, x, y, width, height);
554 	if(this._addNewField){
555 		Dwt.setSize(this._addNewField, Dwt.DEFAULT, 50);
556 	}
557 	this._groupMembersListView.setSize(Dwt.DEFAULT, height-150);
558 
559 	var headerTableHeight = Dwt.getSize(this._headerRow).y;
560 	var tabBarHeight = this._tabBar ? Dwt.getSize(this._tabBar).y : 0; //only DL
561 	var searchFieldsRowHeight = Dwt.getSize(this._searchFieldsRow).y;
562 	var manualAddRowHeight = Dwt.getSize(this._manualAddRow).y;
563 	var navButtonsRowHeight = Dwt.getSize(this._navButtonsRow).y;
564 	var listHeight = height - headerTableHeight - tabBarHeight - searchFieldsRowHeight - manualAddRowHeight - navButtonsRowHeight - 40;
565 	this._listview.setSize(Dwt.DEFAULT, listHeight);
566 };
567 
568 ZmGroupView.prototype.cleanup  =
569 function() {
570 	for (var fieldId in this._searchField) {
571 		this._searchField[fieldId].value = "";
572 	}
573 	this._listview.removeAll(true);
574 	this._groupMembersListView.removeAll(true);
575 	this._addButton.setEnabled(false);
576 	this._addAllButton.setEnabled(false);
577 	this._prevButton.setEnabled(false);
578 	this._nextButton.setEnabled(false);
579 	if (this._addNewField) {
580 		this._addNewField.value = '';
581 	}
582 };
583 
584 
585 // Private methods
586 
587 ZmGroupView.prototype._setFields =
588 function() {
589 	// bug fix #35059 - always reset search-in select since non-zimbra accounts don't support GAL
590 	if (appCtxt.isOffline && appCtxt.accountList.size() > 1 && this._searchInSelect) {
591 		this._searchInSelect.clearOptions();
592 		this._resetSearchInSelect();
593 	}
594 
595 	this._setGroupName();
596 	if (this.isDistributionList()) {
597 		this._setDlFields();
598 	}
599 	this._setGroupMembers();
600 	this._setTags();
601 };
602 
603 ZmGroupView.prototype._setTitle =
604 function(title) {
605 	var div = document.getElementById(this._titleId);
606 	var fileAs = title || this._contact.getFileAs();
607 	div.innerHTML = AjxStringUtil.htmlEncode(fileAs) || (this._contact.id ? " " : ZmMsg.newGroup);
608 };
609 
610 ZmGroupView.prototype._getTagCell =
611 function() {
612 	return document.getElementById(this._tagsId);
613 };
614 
615 ZmGroupView.prototype.getSearchFieldValue =
616 function(fieldId) {
617 	if (!fieldId && !this._detailedSearch) {
618 		fieldId = ZmContactPicker.SEARCH_BASIC;
619 	}
620 	var field = this._searchField[fieldId];
621 	return field && AjxStringUtil.trim(field.value) || "";
622 };
623 
624 ZmGroupView.prototype._createHtml =
625 function() {
626 	this._headerRowId = 		this._htmlElId + "_headerRow";
627 	this._titleId = 			this._htmlElId + "_title";
628 	this._tagsId = 				this._htmlElId + "_tags";
629 	this._groupNameId = 		this._htmlElId + "_groupName";
630 	
631 	if (this.isDistributionList()) {
632 		this._groupNameDomainId = 		this._htmlElId + "_groupNameDomain";
633 		this._allowedDomains = appCtxt.createDistListAllowedDomainsMap;
634 		this._emailDomain = appCtxt.createDistListAllowedDomains[0];
635 		this._emailUsername = "";
636 		var email = this._contact.getEmail();
637 		if (email) {
638 			var temp = email.split("@");
639 			this._emailUsername = temp[0];
640 			this._emailDomain = temp[1];
641 		}
642 		var isCreatingNew = !email;
643 		var domainCount = appCtxt.createDistListAllowedDomains.length;
644 		this._domainEditable = (domainCount > 1) && (isCreatingNew || this._allowedDomains[this._emailDomain]); //since a rename from one domain to another is like deleting on one and creating on other, both require createDistList right on the domains
645 		this._usernameEditable = isCreatingNew || this._allowedDomains[this._emailDomain];
646 
647 		this._dlDisplayNameId = 	this._htmlElId + "_dlDisplayName";
648 		this._dlDescId = 			this._htmlElId + "_dlDesc";
649 		this._dlHideInGalId = 	this._htmlElId + "_dlHideInGal";
650 		this._dlNotesId = 			this._htmlElId + "_dlNotes";
651 		this._subsPolicyOpts = [ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT,
652 							ZmContactSplitView.SUBSCRIPTION_POLICY_APPROVAL,
653 							ZmContactSplitView.SUBSCRIPTION_POLICY_REJECT];
654 		this._dlSubscriptionPolicyId = {};
655 		this._dlUnsubscriptionPolicyId = {};
656 		for (var i = 0; i < this._subsPolicyOpts.length; i++) {
657 			var opt = this._subsPolicyOpts[i];
658 			this._dlSubscriptionPolicyId[opt] = this._htmlElId + "_dlSubscriptionPolicy" + opt; //_dlSubscriptionPolicyACCEPT / APPROVAL / REJECT
659 			this._dlUnsubscriptionPolicyId[opt] = this._htmlElId + "_dlUnsubscriptionPolicy" + opt; //_dlUnsubscriptionPolicyACCEPT / APPROVAL / REJECT
660 		}
661 		this._mailPolicyOpts = [ZmGroupView.MAIL_POLICY_ANYONE,
662 								ZmGroupView.MAIL_POLICY_MEMBERS,
663 								ZmGroupView.MAIL_POLICY_INTERNAL,
664 								ZmGroupView.MAIL_POLICY_SPECIFIC];
665 		this._dlMailPolicyId = {};
666 		for (i = 0; i < this._mailPolicyOpts.length; i++) {
667 			opt = this._mailPolicyOpts[i];
668 			this._dlMailPolicyId[opt] = this._htmlElId + "_dlMailPolicy" + opt; //_dlMailPolicyANYONE / etc
669 		}
670 		this._dlListSpecificMailersId = 	this._htmlElId + "_dlListSpecificMailers";
671 
672 		this._dlListOwnersId = 	this._htmlElId + "_dlListOwners";
673 
674 		// create auto-completer
675 		var params = {
676 			dataClass:		appCtxt.getAutocompleter(),
677 			matchValue:		ZmAutocomplete.AC_VALUE_EMAIL,
678 			keyUpCallback:	ZmGroupView._onKeyUp, 
679 			contextId:		this.toString()
680 		};
681 		this._acAddrSelectList = new ZmAutocompleteListView(params);
682 		if (appCtxt.multiAccounts) {
683 			var acct = object.account || appCtxt.accountList.mainAccount;
684 			this._acAddrSelectList.setActiveAccount(acct);
685 		}
686 	}
687 	this._searchFieldId = 		this._htmlElId + "_searchField";
688 
689 	var showSearchIn = false;
690 	if (appCtxt.get(ZmSetting.CONTACTS_ENABLED)) {
691 		if (appCtxt.get(ZmSetting.GAL_ENABLED) || appCtxt.get(ZmSetting.SHARING_ENABLED))
692 			showSearchIn = true;
693 	}
694 	var params = this._templateParams = {
695 		id: this._htmlElId,
696 		showSearchIn: showSearchIn,
697 		detailed: this._detailedSearch,
698 		contact: this._contact,
699 		isEdit: true,
700 		usernameEditable: this._usernameEditable,
701 		domainEditable: this._domainEditable,
702 		username: this._emailUsername,
703 		domain: this._emailDomain,
704 		addrbook: this._contact.getAddressBook()
705 	};
706 
707 	if (this.isDistributionList()) {
708 		this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#DlView", params);
709 		this._tabViewContainerId = this._htmlElId + "_tabViewContainer";
710 		var tabViewContainer = document.getElementById(this._tabViewContainerId);
711 		this._tabView = new DwtTabView({parent: this, posStyle: Dwt.STATIC_STYLE, id: this._htmlElId + "_tabView"});
712 		this._tabView.reparentHtmlElement(tabViewContainer);
713 		this._dlMembersTabView = new ZmDlMembersTabView(this);
714 		this._tabView.addTab(ZmMsg.dlMembers, this._dlMembersTabView);
715 		this._dlPropertiesTabView = new ZmDlPropertiesTabView(this);
716 		this._tabView.addTab(ZmMsg.dlProperties, this._dlPropertiesTabView);
717 	}
718 	else {
719 		this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#GroupView", params);
720 	}
721 
722 	this._headerRow = document.getElementById(this._headerRowId);
723 	this._tabBar = document.getElementById(this._htmlElId + "_tabView_tabbar"); //only for DLs
724 	this._searchFieldsRow = document.getElementById(this._htmlElId + "_searchFieldsRow");
725 	this._manualAddRow = document.getElementById(this._htmlElId + "_manualAddRow");
726 	this._navButtonsRow = document.getElementById(this._htmlElId + "_navButtonsRow");
727 
728 	this._htmlInitialized = true;
729 };
730 
731 ZmGroupView.prototype._addWidgets =
732 function() {
733 	if (!this.isDistributionList() || this._usernameEditable) {
734 		this._groupNameInput = new DwtInputField({parent:this, size: this.isDistributionList() ? 20: 40, inputId: this._htmlElId + "_groupName"});
735 		this._groupNameInput.setHint(this.isDistributionList() ? ZmMsg.distributionList : ZmMsg.groupNameLabel);
736 		this._groupNameInput.reparentHtmlElement(this._htmlElId + "_groupNameParent");
737 	}
738 	
739 	this._groupMembers = document.getElementById(this._htmlElId + "_groupMembers");
740 	this._noManualEntry = this._groupMembers.disabled; // see bug 23858
741 
742 	// add select menu
743 	var selectId = this._htmlElId + "_listSelect";
744 	var selectCell = document.getElementById(selectId);
745 	if (selectCell) {
746 		this._searchInSelect = new DwtSelect({parent:this});
747 		this._resetSearchInSelect();
748 		this._searchInSelect.reparentHtmlElement(selectId);
749 		this._searchInSelect.addChangeListener(new AjxListener(this, this._searchTypeListener));
750 	}
751 
752 	// add "Search" button
753 	this._searchButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_searchButton")});
754 	this._searchButton.setText(ZmMsg.search);
755 	this._searchButton.addSelectionListener(new AjxListener(this, this._searchButtonListener));
756 
757 	// add list view for search results
758 	this._listview = new ZmGroupListView(this);
759 	this._listview.reparentHtmlElement(this._htmlElId + "_listView");
760 	this._listview.addSelectionListener(new AjxListener(this, this._selectionListener));
761 	this._listview.setUI(null, true); // renders headers and empty list
762 	this._listview._initialized = true;
763 
764 	// add list view for group memebers
765 	this._groupMembersListView = new ZmGroupMembersListView(this);
766 	this._groupMembersListView.reparentHtmlElement(this._htmlElId + "_groupMembers");
767 	this._groupMembersListView.addSelectionListener(new AjxListener(this, this._groupMembersSelectionListener));
768 	this._groupMembersListView.setUI(null, true);
769 	this._groupMembersListView._initialized = true;
770 			
771 	var addListener = new AjxListener(this, this._addListener);
772 	// add "Add" button
773 	this._addButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addButton")});
774 	this._addButton.setText(ZmMsg.add);
775 	this._addButton.addSelectionListener(addListener);
776 	this._addButton.setEnabled(false);
777 	this._addButton.setImage("LeftArrow");
778 
779 	// add "Add All" button
780 	this._addAllButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addAllButton")});
781 	this._addAllButton.setText(ZmMsg.addAll);
782 	this._addAllButton.addSelectionListener(addListener);
783 	this._addAllButton.setEnabled(false);
784 	this._addAllButton.setImage("LeftArrow");
785 
786 	var pageListener = new AjxListener(this, this._pageListener);
787 	// add paging buttons
788 	this._prevButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_prevButton")});
789 	this._prevButton.setImage("LeftArrow");
790 	this._prevButton.addSelectionListener(pageListener);
791 	this._prevButton.setEnabled(false);
792 
793 	this._nextButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_nextButton")});
794 	this._nextButton.setImage("RightArrow");
795 	this._nextButton.addSelectionListener(pageListener);
796 	this._nextButton.setEnabled(false);
797 
798 	this._locationButton = new DwtButton({parent:this, parentElement: (this._htmlElId + "_LOCATION_FOLDER")});
799 	this._locationButton.setImage("ContactsFolder");
800 	this._locationButton.setEnabled(this._contact && !this._contact.isShared() && !this._contact.isDistributionList());
801 	this._locationButton.addSelectionListener(new AjxListener(this, this._handleFolderButton));
802 	var folderOrId = this._contact && this._contact.getAddressBook();
803 	if (!folderOrId) {
804 		var overview = appCtxt.getApp(ZmApp.CONTACTS).getOverview();
805 		folderOrId = overview && overview.getSelected();
806 		if (folderOrId && folderOrId.type != ZmOrganizer.ADDRBOOK) {
807 			folderOrId = null;
808 		}
809 		if (!this.isDistributionList() && folderOrId && folderOrId.id && folderOrId.id == ZmFolder.ID_DLS) { //can't create under Distribution Lists virtual folder
810 			folderOrId = null;
811 		}
812 	}
813 
814 	this._setLocationFolder(folderOrId);
815 	
816 	
817 	// add New Button
818 	this._addNewField = document.getElementById(this._htmlElId + "_addNewField");
819 	if (this._addNewField) {
820 		this._addNewButton = new DwtButton({parent:this, parentElement:(this._htmlElId + "_addNewButton")});
821 		this._addNewButton.setText(ZmMsg.add);
822 		this._addNewButton.addSelectionListener(new AjxListener(this, this._addNewListener));
823 		this._addNewButton.setImage("LeftArrow");
824 	}
825 
826 	var fieldMap = {};
827 	var rowMap = {};
828 	ZmContactPicker.prototype.mapFields.call(this, fieldMap, rowMap);
829 
830 	this._searchField = {};
831 	for (var fieldId in fieldMap) {
832 		var field = Dwt.byId(fieldMap[fieldId]);
833 		if (field) this._searchField[fieldId] = field;
834 	}
835 	
836 	this._searchRow = {};
837 	for (var rowId in rowMap) {
838 		row = Dwt.byId(rowMap[rowId]);
839 		if (row) this._searchRow[rowId] = row;
840 	}
841 	this._updateSearchRows(this._searchInSelect && this._searchInSelect.getValue() || ZmContactsApp.SEARCHFOR_CONTACTS);
842 };
843 
844 ZmGroupView.prototype._installKeyHandlers =
845 function() {
846 
847 	if (this.isDistributionList()) {
848 		if (this._usernameEditable) {
849 			var groupName = document.getElementById(this._groupNameId);
850 			Dwt.setHandler(groupName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
851 			Dwt.associateElementWithObject(groupName, this);
852 		}
853 		if (this._domainEditable) {
854 			var groupNameDomain = document.getElementById(this._groupNameDomainId);
855 			Dwt.setHandler(groupNameDomain, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
856 			Dwt.associateElementWithObject(groupNameDomain, this);
857 		}
858 
859 		var dlDisplayName = document.getElementById(this._dlDisplayNameId);
860 		Dwt.setHandler(dlDisplayName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
861 		Dwt.associateElementWithObject(dlDisplayName, this);
862 
863 		var dlDesc = document.getElementById(this._dlDescId);
864 		Dwt.setHandler(dlDesc, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
865 		Dwt.associateElementWithObject(dlDesc, this);
866 
867 		var dlHideInGal = document.getElementById(this._dlHideInGalId);
868 		Dwt.setHandler(dlHideInGal, DwtEvent.ONCHANGE, ZmGroupView._onChange);
869 		Dwt.associateElementWithObject(dlHideInGal, this);
870 
871 		var dlNotes = document.getElementById(this._dlNotesId);
872 		Dwt.setHandler(dlNotes, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
873 		Dwt.associateElementWithObject(dlNotes, this);
874 
875 		for (var i = 0; i < this._subsPolicyOpts.length; i++) {
876 			var opt =  this._subsPolicyOpts[i];
877 			var policy = document.getElementById(this._dlSubscriptionPolicyId[opt]);
878 			Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange);
879 			Dwt.associateElementWithObject(policy, this);
880 
881 			policy = document.getElementById(this._dlUnsubscriptionPolicyId[opt]);
882 			Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange);
883 			Dwt.associateElementWithObject(policy, this);
884 		}
885 
886 		var dlListOwners = document.getElementById(this._dlListOwnersId);
887 		Dwt.associateElementWithObject(dlListOwners, this);
888 		if (this._acAddrSelectList) {
889 			this._acAddrSelectList.handle(dlListOwners);
890 		}
891 		else {
892 			Dwt.setHandler(dlListOwners, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
893 		}
894 
895 		for (i = 0; i < this._mailPolicyOpts.length; i++) {
896 			opt =  this._mailPolicyOpts[i];
897 			policy = document.getElementById(this._dlMailPolicyId[opt]);
898 			Dwt.setHandler(policy, DwtEvent.ONCHANGE, ZmGroupView._onChange);
899 			Dwt.associateElementWithObject(policy, this);
900 		}
901 		var dlListSpecificMailers = document.getElementById(this._dlListSpecificMailersId);
902 		Dwt.associateElementWithObject(dlListSpecificMailers, this);
903 		if (this._acAddrSelectList) {
904 			this._acAddrSelectList.handle(dlListSpecificMailers);
905 		}
906 		else {
907 			Dwt.setHandler(dlListSpecificMailers, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
908 		}
909 
910 	}
911 	else {
912 		var groupName = document.getElementById(this._groupNameId);
913 		Dwt.setHandler(groupName, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
914 		Dwt.associateElementWithObject(groupName, this);
915 	}
916 
917 	if (!this._noManualEntry) {
918 		Dwt.setHandler(this._groupMembers, DwtEvent.ONKEYUP, ZmGroupView._onKeyUp);
919 		Dwt.associateElementWithObject(this._groupMembers, this);
920 	}
921 
922 	for (var fieldId in this._searchField) {
923 		var searchField = this._searchField[fieldId];
924 		Dwt.setHandler(searchField, DwtEvent.ONKEYPRESS, ZmGroupView._keyPressHdlr);
925 		Dwt.associateElementWithObject(searchField, this);
926 	}
927 };
928 
929 /**
930  * very important method to have in order for the tab group (and tabbing) to be set up correctly (called from ZmBaseController.prototype._initializeTabGroup)
931  */
932 ZmGroupView.prototype.getTabGroupMember = function() {
933 	return this._tabGroup;
934 };
935 
936 ZmGroupView.prototype._getTabGroupMembers =
937 function() {
938 	var fields = [];
939 	if (this.isDistributionList()) {
940 		if (this._usernameEditable) {
941 			fields.push(document.getElementById(this._groupNameId));
942 		}
943 		if (this._domainEditable) {
944 			fields.push(document.getElementById(this._groupNameDomainId));
945 		}
946 		fields.push(document.getElementById(this._dlDisplayNameId));
947 		fields.push(document.getElementById(this._dlDescId));
948 		fields.push(document.getElementById(this._dlHideInGalId));
949 		for (var i = 0; i < this._mailPolicyOpts.length; i++) {
950 			var opt = this._mailPolicyOpts[i];
951 			fields.push(document.getElementById(this._dlMailPolicyId[opt]));
952 		}
953 
954 		for (var i = 0; i < this._subsPolicyOpts.length; i++) {
955 			var opt = this._subsPolicyOpts[i];
956 			fields.push(document.getElementById(this._dlSubscriptionPolicyId[opt]));
957 		}
958 		for (i = 0; i < this._subsPolicyOpts.length; i++) {
959 			opt = this._subsPolicyOpts[i];
960 			fields.push(document.getElementById(this._dlUnsubscriptionPolicyId[opt]));
961 		}
962 		fields.push(document.getElementById(this._dlNotesId));
963 	}
964 	else {
965 		fields.push(document.getElementById(this._groupNameId));
966 	}
967 	if (!this._noManualEntry) {
968 		fields.push(this._groupMembers);
969 	}
970 	for (var fieldId in this._searchField) {
971 		fields.push(this._searchField[fieldId]);
972 	}
973 	fields.push(this._searchButton);
974 	fields.push(this._searchInSelect);
975 
976 	return fields;
977 };
978 
979 ZmGroupView.prototype._getDefaultFocusItem =
980 function() {
981 	if (this.isDistributionList()) {
982 		if (this._usernameEditable) {
983 			return document.getElementById(this._groupNameId);
984 		}
985 		if (this._domainEditable) {
986 			return document.getElementById(this._groupNameDomainId);
987 		}
988 		return document.getElementById(this._dlDisplayNameId);
989 	}
990 	return document.getElementById(this._groupNameId);
991 };
992 
993 ZmGroupView.prototype._getGroupMembers =
994 function() {
995 	return this._groupMembersListView.getList().getArray();
996 };
997 
998 ZmGroupView.prototype._getFolderId =
999 function() {
1000 	return this._folderId || ZmFolder.ID_CONTACTS;
1001 };
1002 
1003 ZmGroupView.prototype._setGroupMembers =
1004 function() {
1005 	var members = this._contact.getAllGroupMembers();
1006 	if (!members) {
1007 		return;
1008 	}
1009 	this._groupMembersListView.set(AjxVector.fromArray(members)); //todo?
1010 };
1011 
1012 ZmGroupView.prototype._setGroupName =
1013 function() {
1014 	if (this.isDistributionList()) {
1015 		if (this._domainEditable) {
1016 			var groupNameDomain = document.getElementById(this._groupNameDomainId);
1017 			groupNameDomain.value = this._emailDomain;
1018 		}
1019 		if (this._usernameEditable) {
1020 			this._groupNameInput.setValue(this._emailUsername);
1021 		}
1022 		return;
1023 	}
1024 	var groupName = document.getElementById(this._groupNameId);
1025 	if (!groupName) {
1026 		return;
1027 	}
1028 
1029 	this._groupNameInput.setValue(this._contact.getFileAs());
1030 };
1031 
1032 ZmGroupView.prototype._setDlFields =
1033 function() {
1034 	var displayName = document.getElementById(this._dlDisplayNameId);
1035 	var dlInfo = this._contact.dlInfo;
1036 	displayName.value = dlInfo.displayName || "";
1037 
1038 	var desc = document.getElementById(this._dlDescId);
1039 	desc.value = dlInfo.description || "";
1040 
1041 	var hideInGal = document.getElementById(this._dlHideInGalId);
1042 	hideInGal.checked = dlInfo.hideInGal;
1043 
1044 	//set the default only in temporary var so it will be saved later as modification, even if user doesn't change.
1045 	//this is for the new DL case
1046 	var subsPolicy = dlInfo.subscriptionPolicy || ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT;
1047 	var unsubsPolicy = dlInfo.unsubscriptionPolicy || ZmContactSplitView.SUBSCRIPTION_POLICY_ACCEPT;
1048 	for (var i = 0; i < this._subsPolicyOpts.length; i++) {
1049 		var opt = this._subsPolicyOpts[i];
1050 		var subsPolicyOpt = document.getElementById(this._dlSubscriptionPolicyId[opt]);
1051 		subsPolicyOpt.checked = subsPolicy == opt;
1052 
1053 		var unsubsPolicyOpt = document.getElementById(this._dlUnsubscriptionPolicyId[opt]);
1054 		unsubsPolicyOpt.checked = unsubsPolicy == opt;
1055 	}
1056 	var mailPolicy = dlInfo.mailPolicy || ZmGroupView.MAIL_POLICY_ANYONE;
1057 	for (i = 0; i < this._mailPolicyOpts.length; i++) {
1058 		opt = this._mailPolicyOpts[i];
1059 		var mailPolicyOpt = document.getElementById(this._dlMailPolicyId[opt]);
1060 		mailPolicyOpt.checked = mailPolicy == opt;
1061 	}
1062 	if (dlInfo.mailPolicy == ZmGroupView.MAIL_POLICY_SPECIFIC) {
1063 		var listSpecificMailers = document.getElementById(this._dlListSpecificMailersId);
1064 		listSpecificMailers.value = dlInfo.mailPolicySpecificMailers.join("; ");
1065 		if (listSpecificMailers.value.length > 0) {
1066 			listSpecificMailers.value += ";"; //so it's ready to add more by user.
1067 		}
1068 	}
1069 
1070 	var listOwners = document.getElementById(this._dlListOwnersId);
1071 	listOwners.value = dlInfo.owners.join("; ");
1072 	if (listOwners.value.length > 0) {
1073 		listOwners.value += ";"; //so it's ready to add more by user.
1074 	}
1075 
1076 	var notes = document.getElementById(this._dlNotesId);
1077 	notes.value = dlInfo.notes || "";
1078 
1079 };
1080 
1081 
1082 ZmGroupView.prototype._resetSearchInSelect =
1083 function() {
1084 	this._searchInSelect.addOption(ZmMsg.contacts, true, ZmContactsApp.SEARCHFOR_CONTACTS);
1085 	if (appCtxt.get(ZmSetting.SHARING_ENABLED)) {
1086 		this._searchInSelect.addOption(ZmMsg.searchPersonalSharedContacts, false, ZmContactsApp.SEARCHFOR_PAS);
1087 	}
1088 	if (appCtxt.get(ZmSetting.GAL_ENABLED) && appCtxt.getActiveAccount().isZimbraAccount) {
1089 		this._searchInSelect.addOption(ZmMsg.GAL, true, ZmContactsApp.SEARCHFOR_GAL);
1090 	}
1091 	if (!appCtxt.get(ZmSetting.INITIALLY_SEARCH_GAL) || !appCtxt.get(ZmSetting.GAL_ENABLED)) {
1092 		this._searchInSelect.setSelectedValue(ZmContactsApp.SEARCHFOR_CONTACTS);
1093 	}
1094 };
1095 
1096 ZmGroupView.prototype._setLocationFolder = function(organizerOrId) {
1097 	if (organizerOrId) {
1098 		var organizer = organizerOrId instanceof ZmOrganizer ? organizerOrId : appCtxt.getById(organizerOrId);
1099 	}
1100 	if (!organizer || organizer.isReadOnly()) {
1101 		//default to the main contacts folder
1102 		organizer = appCtxt.getById(ZmOrganizer.ID_ADDRBOOK);
1103 	}
1104 
1105 	this._locationButton.setText(organizer.getName());
1106 	this._folderId = organizer.id;
1107 };
1108 
1109 ZmGroupView.prototype._handleFolderButton = function(ev) {
1110 	var dialog = appCtxt.getChooseFolderDialog();
1111 	dialog.registerCallback(DwtDialog.OK_BUTTON, new AjxCallback(this, this._handleChooseFolder));
1112 	var params = {
1113 		overviewId:		dialog.getOverviewId(ZmApp.CONTACTS),
1114 		title:			ZmMsg.chooseAddrBook,
1115 		treeIds:		[ZmOrganizer.ADDRBOOK],
1116 		skipReadOnly:	true,
1117 		skipRemote:		false,
1118 		noRootSelect:	true,
1119 		appName:		ZmApp.CONTACTS
1120 	};
1121 	params.omit = {};
1122 	params.omit[ZmFolder.ID_TRASH] = true;
1123 	dialog.popup(params);
1124 };
1125 
1126 /**
1127  * @private
1128  */
1129 ZmGroupView.prototype._handleChooseFolder = function(organizer) {
1130 	var dialog = appCtxt.getChooseFolderDialog();
1131 	dialog.popdown();
1132 	this._isDirty = true;
1133 	this._setLocationFolder(organizer);
1134 };
1135 
1136 ZmGroupView.prototype._setTags =
1137 function() {
1138 	var tagCell = this._getTagCell();
1139 	if (!tagCell) { return; }
1140 
1141 	tagCell.innerHTML = ZmTagsHelper.getTagsHtml(this._contact, this);
1142 };
1143 
1144 // Consistent spot to locate various dialogs
1145 ZmGroupView.prototype._getDialogXY =
1146 function() {
1147 	if (this.isDistributionList()) {
1148 		// the scrolling messes up the calculation of Dwt.toWindow. This however seems to work fine, the dialog is just a little higher than other cases
1149 		return new DwtPoint(ZmGroupView.DIALOG_X, ZmGroupView.DIALOG_Y);
1150 	}
1151 	var loc = Dwt.toWindow(this.getHtmlElement(), 0, 0);
1152 	return new DwtPoint(loc.x + ZmGroupView.DIALOG_X, loc.y + ZmGroupView.DIALOG_Y);
1153 };
1154 
1155 // Listeners
1156 
1157 ZmGroupView.prototype._groupMembersSelectionListener =
1158 function(ev){
1159 	var selection = this._groupMembersListView.getSelection();
1160 	if (ev && ev.target && this._groupMembersListView.delButtons[ev.target.id]) {
1161 		this._delListener(ev);	
1162 	}
1163 	else if (ev && ev.target && this._groupMembersListView.quickAddButtons[ev.target.id]) {
1164 		if (AjxUtil.isArray(selection)) {
1165 			var address = selection[0].address || selection[0];
1166 			this.quickAddContact(address);
1167 		}
1168 	}
1169 		
1170 };
1171 
1172 ZmGroupView.prototype._selectionListener =
1173 function(ev) {
1174 	var selection = this._listview.getSelection();
1175 
1176 	if (ev.detail == DwtListView.ITEM_DBL_CLICKED) {
1177 		this._addItems(selection);
1178 	} else {
1179 		this._addButton.setEnabled(selection.length > 0);
1180 	}
1181 };
1182 
1183 ZmGroupView.prototype._selectChangeListener =
1184 function(ev) {
1185 	this._attr[ZmContact.F_folderId] = ev._args.newValue;
1186 	this._isDirty = true;
1187 };
1188 
1189 ZmGroupView.prototype._searchTypeListener =
1190 function(ev) {
1191 	var oldValue = ev._args.oldValue;
1192 	var newValue = ev._args.newValue;
1193 
1194 	if (oldValue != newValue) {
1195 		this._updateSearchRows(newValue);
1196 		this._searchButtonListener();
1197 	}
1198 };
1199 
1200 ZmGroupView.prototype._delListener =
1201 function(ev){
1202 
1203 	var items = this._groupMembersListView.getSelection();
1204 	var selectedDomItems = this._groupMembersListView.getSelectedItems();
1205 
1206 	while (selectedDomItems.get(0)) {
1207 		this._groupMembersListView.removeItem(selectedDomItems.get(0));
1208 	}
1209 
1210 	for (var i = 0;  i < items.length; i++) {
1211 		var item = items[i];
1212 		this._groupMembersListView.getList().remove(item);
1213 		var contact = item.__contact;
1214 		var type = item.type;
1215 		var value = item.groupRefValue || item.value;
1216 
1217 		//var value = item.value || (contact ? contact.getId(!contact.isGal) : item);
1218 
1219 		if (!this._groupMemberMods[value]) {
1220 			this._groupMemberMods[value] = {op : "-", value : value, email: item.address, type : type};
1221 		}
1222 		else {
1223 			this._groupMemberMods[value] = {};
1224 		}
1225 	}
1226 
1227 	this._groupMembersSelectionListener();
1228 	this._isDirty = true;
1229 };
1230 
1231 ZmGroupView.prototype._addNewListener =
1232 function(ev){
1233 	var emailStr = this._addNewField.value;
1234 	if (!emailStr || emailStr == '') { return; }
1235 
1236 	var allArray = AjxEmailAddress.parseEmailString(emailStr).all.getArray(); //in bug 38907 it was changed to "all" instead of "good". No idea why. So we can now add bad email addresses. Is that on purpose?
1237 	var addrs = [];
1238 	for (var i = 0; i < allArray.length; i++) {
1239 		addrs.push(ZmContactsHelper._wrapInlineContact(allArray[i].address)); //might be better way to do this, we recreate the AjxEmailAddress just to add the "value" and "type" and maybe "id" attributes.
1240 	}
1241 
1242 	addrs = ZmGroupView._dedupe(addrs, this._groupMembersListView.getList().getArray());
1243 	this._addToMembers(addrs);
1244 
1245 	this._addNewField.value = '';
1246 };
1247 
1248 ZmGroupView.prototype._addListener =
1249 function(ev) {
1250 	var list = (ev.dwtObj == this._addButton)
1251 		? this._listview.getSelection()
1252 		: this._listview.getList().getArray();
1253 
1254 	this._addItems(list);
1255 };
1256 
1257 ZmGroupView.prototype._addItems =
1258 function(list) {
1259 	if (list.length == 0) { return; }
1260 
1261 	// we have to walk the results in case we hit a group which needs to be split
1262 	var items = [];
1263 	for (var i = 0; i < list.length; i++) {
1264 		var item = list[i];
1265 		var contact = item.__contact;
1266 		if (item.isGroup && !contact.isDistributionList()) {
1267 			var groupMembers = contact.attr[ZmContact.F_groups];
1268 			for (var j = 0; j < groupMembers.length; j++) {
1269 				var value = groupMembers[j].value;
1270 				var memberContact = ZmContact.getContactFromCache(value);
1271 				var obj;
1272 				if (memberContact) {
1273 					obj = ZmContactsHelper._wrapContact(memberContact);
1274 				}
1275 				else {
1276 					obj = ZmContactsHelper._wrapInlineContact(value);
1277 				}
1278 				if (obj) {
1279 					items.push(obj);
1280 				}
1281 			}
1282 		}
1283 		else {
1284 			items.push(list[i]);
1285             if (contact.isGal) {
1286                 appCtxt.cacheSet(contact.ref, contact); //not sure why we do this. just seems like maybe we should do this elsewhere in more consistent way. 
1287             }
1288 		}
1289 	}
1290 
1291 	items = ZmGroupView._dedupe(items, this._groupMembersListView.getList().getArray());
1292 	if (items.length > 0) {
1293 		this._addToMembers(items);
1294 	}
1295 };
1296 
1297 ZmGroupView.prototype._addToMembers =
1298 function(items){
1299 	var userZid = appCtxt.accountList.mainAccount.id;
1300 	for (var i = 0; i < items.length; i++) {
1301 		var item = items[i];
1302 		var type = item.type;
1303 		var value = item.value;
1304 		var email = item.address;
1305 		var obj = this._groupMemberMods[value];
1306 		if (!obj) {
1307 			if (type === ZmContact.GROUP_CONTACT_REF && value && value.indexOf(":") === -1 ) {
1308 				value = userZid + ":" + value;
1309 			}
1310 			this._groupMemberMods[value] = {op : "+", value : value, type : type, email: email};
1311 		}
1312 		else if (obj.op == "-") {
1313 			//contact is already in the group, clear the value
1314 			this._groupMemberMods[value] = {};
1315 		}
1316 	}
1317 	var membersList = this._groupMembersListView.getList();
1318 	items = items.concat(membersList ? membersList.getArray() : []);
1319 	this._isDirty = true;
1320 
1321 	this._groupMembersListView.set(AjxVector.fromArray(items));
1322 };
1323 
1324 /**
1325  * Returns the items from newItems that are not in list, and also not duplicates within newItems (i.e. returns one of each)
1326  *
1327  * @param newItems {Array} array of items to be added to the target list
1328  * @param list {Array} the target list as an array of items
1329  * @return {Array} uniqueNewItems the unique new items (items that are not in the list or duplicates in the newItems)
1330  * @private
1331  */
1332 ZmGroupView._dedupe =
1333 function(newItems, list) {
1334 
1335 	AjxUtil.dedup(newItems, function(item) {
1336 		return item.type + "$" + item.value;
1337 	});
1338 
1339 	var uniqueNewItems = [];
1340 
1341 	for (var i = 0; i < newItems.length; i++) {
1342 		var newItem = newItems[i];
1343 		var found = false;
1344 		for (var j = 0; j < list.length; j++) {
1345 			var item = list[j];
1346 			if (newItem.type == item.type && newItem.value == item.value) {
1347 				found = true;
1348 				break;
1349 			}
1350 		}
1351 		if (!found) {
1352 			uniqueNewItems.push(newItem);
1353 		}
1354 	}
1355 
1356 	return uniqueNewItems;
1357 };
1358 
1359 ZmGroupView.prototype._setGroupMemberValue =
1360 function(value, append) {
1361 	if (this._noManualEntry) {
1362 		this._groupMembers.disabled = false;
1363 	}
1364 
1365 	if (append) {
1366 		this._groupMembers.value += value;
1367 	} else {
1368 		this._groupMembers.value = value;
1369 	}
1370 
1371 	if (this._noManualEntry) {
1372 		this._groupMembers.disabled = true;
1373 	}
1374 };
1375 
1376 
1377 /**
1378  * called from ZmContactPicker.prototype._showResults
1379  * @param list
1380  */
1381 ZmGroupView.prototype._setResultsInView =
1382 function(list) {
1383 	var arr = list.getArray();
1384 	this._listview.setItems(arr);
1385 	this._addButton.setEnabled(arr.length > 0);
1386 	this._addAllButton.setEnabled(arr.length > 0);
1387 };
1388 
1389 /**
1390  * called from ZmContactPicker.prototype._showResults
1391  */
1392 ZmGroupView.prototype._setNoResultsHtml =
1393 function(list) {
1394 	//no need to do anything here. the setItems called from _setResultsInView sets the "no results found" if list is empty. (via call to addItems)
1395 };
1396 
1397 /**
1398  * reuse functions from ZmContactPicker, some called from ZmContactPicker code we re-use here.
1399  */
1400 ZmGroupView.prototype.search = ZmContactPicker.prototype.search;
1401 ZmGroupView.prototype._handleResponseSearch = ZmContactPicker.prototype._handleResponseSearch;
1402 ZmGroupView.prototype._resetResults = ZmContactPicker.prototype._resetResults;
1403 ZmGroupView.prototype._searchButtonListener = ZmContactPicker.prototype._searchButtonListener;
1404 ZmGroupView.prototype._pageListener = ZmContactPicker.prototype._pageListener;
1405 ZmGroupView.prototype._showResults = ZmContactPicker.prototype._showResults;
1406 ZmGroupView.prototype.getSubList = ZmContactPicker.prototype.getSubList;
1407 
1408 
1409 ZmGroupView.prototype._tagChangeListener = function(ev) {
1410 	if (ev.type != ZmEvent.S_TAG) { return; }
1411 
1412 	var fields = ev.getDetail("fields");
1413 	var changed = fields && (fields[ZmOrganizer.F_COLOR] || fields[ZmOrganizer.F_NAME]);
1414 	if ((ev.event == ZmEvent.E_MODIFY && changed) || ev.event == ZmEvent.E_DELETE || ev.event == ZmEvent.MODIFY) {
1415 		this._setTags();
1416 	}
1417 };
1418 
1419 ZmGroupView.prototype._groupChangeListener = function(ev) {
1420 	if (ev.type != ZmEvent.S_CONTACT) return;
1421 	if (ev.event == ZmEvent.E_TAGS || ev.event == ZmEvent.E_REMOVE_ALL) {
1422 		this._setTags();
1423 	}
1424 };
1425 
1426 ZmGroupView.prototype._updateSearchRows =
1427 function(searchFor) {
1428 	var fieldIds = (searchFor == ZmContactsApp.SEARCHFOR_GAL) ? ZmContactPicker.SHOW_ON_GAL : ZmContactPicker.SHOW_ON_NONGAL;
1429 	for (var fieldId in this._searchRow) {
1430 		Dwt.setVisible(this._searchRow[fieldId], AjxUtil.indexOf(fieldIds, fieldId)!=-1);
1431 	}
1432 };
1433 
1434 ZmGroupView.prototype._resetSearchColHeaders =
1435 function() {
1436 	var lv = this._listview;
1437 	lv.headerColCreated = false;
1438 	var isGal = this._searchInSelect && (this._searchInSelect.getValue() == ZmContactsApp.SEARCHFOR_GAL);
1439 
1440 	for (var i = 0; i < lv._headerList.length; i++) {
1441 		var field = lv._headerList[i]._field;
1442 		if (field == ZmItem.F_DEPARTMENT) {
1443 			lv._headerList[i]._visible = isGal && this._detailedSearch;
1444 		}
1445 	}
1446 
1447 	var sortable = isGal ? null : ZmItem.F_NAME;
1448 	lv.createHeaderHtml(sortable);
1449 };
1450 
1451 ZmGroupView.prototype._checkItemCount =
1452 function() {
1453 	this._listview._checkItemCount();
1454 };
1455 
1456 ZmGroupView.prototype._handleResponseCheckReplenish =
1457 function(skipSelection) {
1458 	this._listview._handleResponseCheckReplenish(skipSelection);
1459 };
1460 
1461 // Static methods
1462 
1463 ZmGroupView._onKeyUp =
1464 function(ev) {
1465 	ev = DwtUiEvent.getEvent(ev);
1466 
1467 	var key = DwtKeyEvent.getCharCode(ev);
1468 	if (DwtKeyMapMgr.hasModifier(ev) || DwtKeyMap.IS_MODIFIER[key] ||	key === DwtKeyEvent.KEY_TAB) { return; }
1469 
1470 	var e = DwtUiEvent.getTarget(ev);
1471 	var view = e ? Dwt.getObjectFromElement(e) : null;
1472 	if (view) {
1473 		view._isDirty = true;
1474 	}
1475 
1476 	return true;
1477 };
1478 
1479 ZmGroupView._onChange =
1480 function(ev) {
1481 	ev = DwtUiEvent.getEvent(ev);
1482 
1483 	var e = DwtUiEvent.getTarget(ev);
1484 	var view = e ? Dwt.getObjectFromElement(e) : null;
1485 	if (view) {
1486 		view._isDirty = true;
1487 	}
1488 
1489 	return true;
1490 };
1491 
1492 ZmGroupView._keyPressHdlr =
1493 function(ev) {
1494 	ev = DwtUiEvent.getEvent(ev);
1495 	if (DwtKeyMapMgr.hasModifier(ev)) { return; }
1496 
1497 	var e = DwtUiEvent.getTarget(ev);
1498 	var view = e ? Dwt.getObjectFromElement(e) : null;
1499 	if (view) {
1500 		var charCode = DwtKeyEvent.getCharCode(ev);
1501 		if (charCode == 13 || charCode == 3) {
1502 			view._searchButtonListener(ev);
1503 			return false;
1504 		}
1505 	}
1506 	return true;
1507 };
1508 
1509 ZmGroupView.prototype.quickAddContact = 
1510 function(email) {
1511 	var quickAdd = appCtxt.getContactQuickAddDialog();
1512 	quickAdd.setFields(email);
1513 	var saveCallback = new AjxCallback(this, this._handleQuickAddContact);
1514 	quickAdd.popup(saveCallback);
1515 };
1516 
1517 ZmGroupView.prototype._handleQuickAddContact = 
1518 function(result) {
1519 	var resp = AjxUtil.get(result, "_data", "BatchResponse", "CreateContactResponse");
1520 	var contact = resp ? ZmContact.createFromDom(resp[0].cn[0], {}) : null;
1521 	if (!contact) {
1522 		return;
1523 	}
1524 	var selection = this._groupMembersListView.getSelection();
1525 	var selectedItem = selection[0];
1526 	var value = selectedItem.value;
1527 	if (!this._groupMemberMods[value]) {
1528 		this._groupMemberMods[value] = {op : "-", value : value, type : selectedItem.type};
1529 	}
1530 	else {
1531 		this._groupMemberMods[value] = {};
1532 	}
1533 	var domList = this._groupMembersListView.getSelectedItems();
1534 	this._groupMembersListView.removeItem(domList.get(0));
1535 	this._groupMembersListView.getList().remove(selectedItem);
1536 
1537 	var obj = ZmContactsHelper._wrapContact(contact);
1538 	if (obj) {
1539 		this._addItems([obj]);
1540 	}
1541 };
1542 
1543 /**
1544  * Creates a group list view for search results
1545  * @constructor
1546  * @class
1547  *
1548  * @param {ZmGroupView}		parent			containing widget
1549  * 
1550  * @extends		DwtListView
1551  * 
1552  * @private
1553 */
1554 ZmGroupListView = function(parent) {
1555 	if (arguments.length == 0) { return; }
1556 	DwtListView.call(this, {parent:parent, className:"DwtChooserListView ZmEditGroupContact",
1557 							headerList:this._getHeaderList(parent), view:this._view, posStyle: Dwt.RELATIVE_STYLE});
1558 	Dwt.setScrollStyle(this._elRef, Dwt.CLIP);
1559 };
1560 
1561 ZmGroupListView.prototype = new DwtListView;
1562 ZmGroupListView.prototype.constructor = ZmGroupListView;
1563 
1564 ZmGroupListView.prototype.setItems =
1565 function(items) {
1566 	this._resetList();
1567 	this.addItems(items);
1568 	var list = this.getList();
1569 	if (list && list.size() > 0) {
1570 		this.setSelection(list.get(0));
1571 	}
1572 };
1573 
1574 ZmGroupListView.prototype._getHeaderList =
1575 function() {
1576 	return [
1577 		(new DwtListHeaderItem({field:ZmItem.F_TYPE,	icon:"Contact",		width:ZmMsg.COLUMN_WIDTH_TYPE_CN})),
1578 		(new DwtListHeaderItem({field:ZmItem.F_NAME,	text:ZmMsg._name,	width:ZmMsg.COLUMN_WIDTH_NAME_CN, resizeable: true})),
1579 		(new DwtListHeaderItem({field:ZmItem.F_EMAIL,	text:ZmMsg.email}))
1580 	];
1581 };
1582 
1583 ZmGroupListView.prototype._getCellContents =
1584 function(html, idx, item, field, colIdx, params) {
1585 	return ZmContactsHelper._getEmailField(html, idx, item, field, colIdx, params);
1586 };
1587 
1588 ZmGroupListView.prototype._itemClicked =
1589 function(clickedEl, ev) {
1590 	// Ignore right-clicks, we don't support action menus
1591 	if (!ev.shiftKey && !ev.ctrlKey && ev.button == DwtMouseEvent.RIGHT) { return; }
1592 
1593 	DwtListView.prototype._itemClicked.call(this, clickedEl, ev);
1594 };
1595 
1596 ZmGroupListView.prototype._mouseDownAction =
1597 function(ev, div) {
1598 	return !Dwt.ffScrollbarCheck(ev);
1599 };
1600 
1601 ZmGroupListView.prototype._mouseUpAction =
1602 function(ev, div) {
1603 	return !Dwt.ffScrollbarCheck(ev);
1604 };
1605 
1606 //stub method
1607 ZmGroupListView.prototype._checkItemCount =
1608 function() {
1609 	return true;
1610 };
1611 
1612 //stub method
1613 ZmGroupListView.prototype._handleResponseCheckReplenish =
1614 function() {
1615 	return true;
1616 };
1617 
1618 /**
1619  * Creates a group members list view
1620  * @constructor
1621  * @class
1622  *
1623  * @param {ZmGroupView}	parent			containing widget
1624  * 
1625  * @extends		ZmGroupListView
1626  * 
1627  * 
1628  * @private
1629  */
1630 ZmGroupMembersListView = function (parent) {
1631 	if (arguments.length == 0) { return; }
1632 	ZmGroupListView.call(this, parent);
1633 	this._list = new AjxVector();
1634 	// hash of delete icon IDs
1635 	this.delButtons = {};
1636 	this.quickAddButtons = {};
1637 };
1638 
1639 ZmGroupMembersListView.prototype = new ZmGroupListView;
1640 ZmGroupMembersListView.prototype.constructor = ZmGroupMembersListView;
1641 
1642 ZmGroupMembersListView.prototype._getHeaderList =
1643 function() {
1644 	return [(new DwtListHeaderItem({field:ZmItem.F_EMAIL, text:ZmMsg.membersLabel, view:this._view}))];
1645 };
1646 
1647 ZmGroupMembersListView.prototype._getCellContents =
1648 function(html, idx, item, field, colIdx, params) {
1649 	if (field == ZmItem.F_EMAIL) {
1650 		var data = {};
1651 		data.isEdit = true;
1652 		data.delButtonId = Dwt.getNextId("DelContact_");
1653 		this.delButtons[data.delButtonId] = true;
1654 		var contact = item.__contact;
1655 		var addr = item.address;
1656 
1657 		if (contact && !this.parent.isDistributionList()) {
1658 			data.imageUrl = contact.getImageUrl();
1659 			data.email = AjxStringUtil.htmlEncode(contact.getEmail());
1660 			data.title = AjxStringUtil.htmlEncode(contact.getAttr(ZmContact.F_jobTitle));
1661 			data.phone = AjxStringUtil.htmlEncode(contact.getPhone());
1662 			data.imgClassName = contact.getIconLarge(); 
1663 			var isPhonetic  = appCtxt.get(ZmSetting.PHONETIC_CONTACT_FIELDS);
1664 			var fullnameHtml= contact.getFullNameForDisplay(isPhonetic);
1665 			if (!isPhonetic) {
1666 				fullnameHtml = AjxStringUtil.htmlEncode(fullnameHtml);
1667 			}
1668 			data.fullName = fullnameHtml;
1669 		}
1670 		else {
1671 			data.imgClassName = "PersonInline_48";
1672 			data.email = AjxStringUtil.htmlEncode(addr);
1673 			if (!this.parent.isDistributionList()) {
1674 				data.isInline = true;
1675 				data.quickAddId = Dwt.getNextId("QuickAdd_");
1676 				this.quickAddButtons[data.quickAddId] = true;
1677 			}
1678 		}
1679 		html[idx++] = AjxTemplate.expand("abook.Contacts#SplitView_group", data);
1680 
1681 	}
1682 	return idx;
1683 };
1684 
1685 
1686 // override from base class since it is handled differently
1687 ZmGroupMembersListView.prototype._getItemId =
1688 function(item) {
1689 	return (item && item.id) ? item.id : Dwt.getNextId();
1690 };
1691 
1692 /**
1693  * @class
1694  *
1695  * @param	{DwtControl}	parent		    the parent (dialog)
1696  * @param	{String}	    className		the class name
1697  *
1698  * @extends		DwtTabViewPage
1699  */
1700 ZmDlPropertiesTabView = function(parent, className) {
1701     if (arguments.length == 0) return;
1702 
1703     DwtTabViewPage.call(this, parent, className, Dwt.ABSOLUTE_STYLE);
1704 
1705 	this.setScrollStyle(Dwt.SCROLL);
1706 
1707 	var htmlEl = this.getHtmlElement();
1708 	htmlEl.style.top = this.parent._tabView.getY() + this.parent._tabView._tabBar.getH() + "px";
1709 	htmlEl.style.bottom = 0;
1710 
1711 };
1712 
1713 ZmDlPropertiesTabView.prototype = new DwtTabViewPage;
1714 
1715 ZmDlPropertiesTabView.prototype.toString = function() {
1716 	return "ZmDlPropertiesTabView";
1717 };
1718 
1719 ZmDlPropertiesTabView.prototype._createHtml =
1720 function () {
1721 	DwtTabViewPage.prototype._createHtml.call(this);
1722 	this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#DlPropertiesView", this.parent._templateParams);
1723 };
1724 
1725 /**
1726  * @class
1727  *
1728  * @param	{DwtControl}	parent		    the parent (dialog)
1729  * @param	{String}	    className		the class name
1730  *
1731  * @extends		DwtTabViewPage
1732  */
1733 ZmDlMembersTabView = function(parent, className) {
1734     if (arguments.length == 0) return;
1735 
1736     DwtTabViewPage.call(this, parent, className, Dwt.RELATIVE_STYLE);
1737 
1738 };
1739 
1740 ZmDlMembersTabView.prototype = new DwtTabViewPage;
1741 
1742 ZmDlMembersTabView.prototype.toString = function() {
1743 	return "ZmDlMembersTabView";
1744 };
1745 
1746 ZmDlMembersTabView.prototype._createHtml =
1747 function () {
1748 	DwtTabViewPage.prototype._createHtml.call(this);
1749 	this.getHtmlElement().innerHTML = AjxTemplate.expand("abook.Contacts#GroupViewMembers", this.parent._templateParams);
1750 };
1751 
1752