1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 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) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a Zimlets preference page.
 26  * @constructor
 27  * @class ZmZimletsPage
 28  * This class represents a page that allows the user to enable/disable available
 29  * zimlets. User can see all the simlets those are enabled by admin for his account.
 30  * Out of these available zimlets user can choose some or all for his account.
 31  *
 32  * @author Rajendra Patil
 33  *
 34  * @param {DwtControl}	parent			the containing widget
 35  * @param {Object}	section			the page
 36  * @param {ZmPrefController}	controller		the prefs controller
 37  * 
 38  * @extends		ZmPreferencesPage
 39  * @private
 40  */
 41 ZmZimletsPage = function(parent, section, controller) {
 42 	ZmPreferencesPage.call(this, parent, section, controller);
 43 };
 44 
 45 ZmZimletsPage.prototype = new ZmPreferencesPage;
 46 ZmZimletsPage.prototype.constructor = ZmZimletsPage;
 47 
 48 // CONSTS
 49 ZmZimletsPage.ADMIN_URI = [
 50 	"http://",
 51 	location.hostname, ":",
 52 	location.port,
 53 	"/service/admin/soap/"
 54 ].join("");
 55 
 56 ZmZimletsPage.ADMIN_UPLOAD_URI = [
 57 	"http://",
 58 	location.hostname, ":",
 59 	location.port,
 60 	"/service/upload"
 61 ].join("");
 62 
 63 ZmZimletsPage.ADMIN_COOKIE_NAME				= "ZM_ADMIN_AUTH_TOKEN";
 64 ZmZimletsPage.ZIMLET_UPLOAD_STATUS_PENDING	= "pending";
 65 ZmZimletsPage.ZIMLET_UPLOAD_STATUS_SUCCESS	= "succeeded";
 66 ZmZimletsPage.ZIMLET_UPLOAD_STATUS_FAIL		= "failed";
 67 ZmZimletsPage.ZIMLET_MAX_CHECK_STATUS		= 8;
 68 
 69 ZmZimletsPage.prototype.toString =
 70 function () {
 71 	return "ZmZimletsPage";
 72 };
 73 
 74 ZmZimletsPage.prototype.reset =
 75 function(){
 76 	var arr = this.getZimletsArray();
 77 	for (var i = 0; i < arr.length; i++) {
 78 		arr[i].restoreStatus();
 79 	}
 80 	this.showMe();
 81 };
 82 
 83 ZmZimletsPage.prototype.showMe =
 84 function(deferred){
 85 	ZmPreferencesPage.prototype.showMe.call(this);
 86 	var zimlets = this.getZimlets();
 87 	if (zimlets.size() === 0) {
 88 		this._zimlets = null; //otherwise it would stay cached and would not update when this method is calledback.
 89 		if (!deferred) {
 90 			appCtxt.getAppController().addListener(ZmAppEvent.POST_STARTUP, new AjxListener(this, this.showMe, [true]));
 91 		}
 92 		return;
 93 	}
 94 	if (this._listView) {
 95 		var s = this._listView.getSelection();
 96 		this._listView.set(zimlets.clone());
 97 		if (s && s[0]) {
 98 			this._listView.setSelection(s[0]);
 99 		}
100 	}
101 };
102 
103 ZmZimletsPage.prototype._getTemplateData =
104 function() {
105 	var data = ZmPreferencesPage.prototype._getTemplateData.apply(this, arguments);
106 	if (appCtxt.isOffline) {
107 		data.action = ZmZimletsPage.ADMIN_UPLOAD_URI;
108 	}
109 	return data;
110 };
111 
112 /**
113  * @private
114  */
115 ZmZimletsPage.prototype._setupCustom =
116 function(id, setup, value) {
117 	if (id == ZmSetting.CHECKED_ZIMLETS) {
118 		this._listView = new ZmPrefZimletListView(this, this._controller);
119 		return this._listView;
120 	}
121 
122 	return ZmPreferencesPage.prototype._setupCustom.apply(this, arguments);
123 };
124 
125 ZmZimletsPage.prototype._createControls =
126 function() {
127 	if (appCtxt.isOffline) {
128 		// add "upload" button
129 		this._uploadButton = new DwtButton({parent:this, parentElement: this._htmlElId+"_button"});
130 		this._uploadButton.setText(ZmMsg.uploadNewFile);
131 		this._uploadButton.addSelectionListener(new AjxListener(this, this._fileUploadListener));
132 	}
133 
134 	ZmPreferencesPage.prototype._createControls.apply(this, arguments);
135 };
136 
137 ZmZimletsPage.prototype._fileUploadListener =
138 function() {
139 	this._uploadButton.setEnabled(false);
140 
141 	// first, fetch the admin auth token if none exists
142 	var callback = new AjxCallback(this, this._uploadZimlet);
143 	this._getAdminAuth(callback);
144 };
145 
146 ZmZimletsPage.prototype._uploadZimlet =
147 function() {
148 	this._checkStatusCount = 0;
149 
150 	var callback = new AjxCallback(this, this._handleZimletUpload);
151 	var formEl = document.getElementById(this._htmlElId + "_form");
152 	var um = window._uploadManager = appCtxt.getUploadManager();
153 	um.execute(callback, formEl);
154 };
155 
156 ZmZimletsPage.prototype._handleZimletUpload =
157 function(status, aid) {
158 	if (status == 200) {
159 		this._deployZimlet(aid);
160 	}
161 	else {
162 		var msg = (status == AjxPost.SC_NO_CONTENT)
163 			? ZmMsg.zimletUploadError
164 			: (AjxMessageFormat.format(ZmMsg.zimletUploadStatus, status));
165         appCtxt.setStatusMsg(msg, ZmStatusView.LEVEL_CRITICAL);
166 
167 		this._uploadButton.setEnabled(true);
168 	}
169 };
170 
171 ZmZimletsPage.prototype._getAdminAuth =
172 function(callback) {
173 	// first, make sure we have a valid admin password (parsed from location)
174 	var pword;
175 	var searches = document.location.search.split("&");
176 	for (var i = 0; i < searches.length; i++) {
177 		var idx = searches[i].indexOf("at=");
178 		if (idx != -1) {
179 			pword = searches[i].substring(idx+3);
180 			break;
181 		}
182 	}
183 
184 	// for dev build
185 	if (!pword) {
186 		pword = "@install.key@";
187 	}
188 
189 	var soapDoc = AjxSoapDoc.create("AuthRequest", "urn:zimbraAdmin");
190 	soapDoc.set("name", "zimbra");
191 	soapDoc.set("password", pword);
192 
193 	var params = {
194 		soapDoc: soapDoc,
195 		noSession: true,
196 		asyncMode: true,
197 		noAuthToken: true,
198 		skipAuthCheck: true,
199 		serverUri: ZmZimletsPage.ADMIN_URI,
200 		callback: (new AjxCallback(this, this._handleResponseAuthenticate, [callback]))
201 	};
202 	(new ZmCsfeCommand()).invoke(params);
203 };
204 
205 ZmZimletsPage.prototype._handleResponseAuthenticate =
206 function(callback, result) {
207 	if (result.isException()) {
208 		this._handleZimletDeployError();
209 		return;
210 	}
211 
212 	// set the admin auth cookie (expires when browser closes)
213 	this._adminAuthToken = result.getResponse().Body.AuthResponse.authToken[0]._content;
214 	AjxCookie.setCookie(document, ZmZimletsPage.ADMIN_COOKIE_NAME, this._adminAuthToken, null, "/service/upload");
215 
216 	if (callback) {
217 		callback.run();
218 	}
219 };
220 
221 ZmZimletsPage.prototype._deployZimlet =
222 function(aid, action) {
223 	var dialog = appCtxt.getCancelMsgDialog();
224 	dialog.reset();
225 	dialog.setMessage(ZmMsg.zimletDeploying, DwtMessageDialog.INFO_STYLE);
226 	dialog.registerCallback(DwtDialog.CANCEL_BUTTON, new AjxCallback(this, this._handleZimletCancel, [dialog]));
227 	dialog.popup();
228 
229 	var soapDoc = AjxSoapDoc.create("DeployZimletRequest", "urn:zimbraAdmin");
230 	var method = soapDoc.getMethod();
231 	method.setAttribute("action", (action || "deployLocal"));
232 	method.setAttribute("flush", "1");
233 	method.setAttribute("synchronous", "1");
234 	var content = soapDoc.set("content");
235 	content.setAttribute("aid", aid);
236 
237 	var params = {
238 		soapDoc: soapDoc,
239 		asyncMode: true,
240 		skipAuthCheck: true,
241 		callback: (new AjxCallback(this, this._deployZimletResponse, [dialog, aid])),
242 		serverUri: ZmZimletsPage.ADMIN_URI,
243 		authToken: this._adminAuthToken
244 	};
245 
246 	(new ZmCsfeCommand()).invoke(params);
247 };
248 
249 ZmZimletsPage.prototype._deployZimletResponse =
250 function(dialog, aid, result) {
251     
252     // remove Admin auth key
253      AjxCookie.deleteCookie(document, ZmZimletsPage.ADMIN_COOKIE_NAME, "/service/upload");
254 
255 	if (result.isException()) {
256 		dialog.popdown();
257 		this._uploadButton.setEnabled(true);
258 
259 		this._controller.popupErrorDialog(ZmMsg.zimletDeployError, result.getException());
260 		return;
261 	}
262 
263 	var status = result.getResponse().Body.DeployZimletResponse.progress[0].status;
264 	if (status == ZmZimletsPage.ZIMLET_UPLOAD_STATUS_PENDING) {
265 		if (this._checkStatusCount++ > ZmZimletsPage.ZIMLET_MAX_CHECK_STATUS) {
266 			this._handleZimletDeployError();
267 		} else {
268 			AjxTimedAction.scheduleAction(new AjxTimedAction(this, this._deployZimlet, [aid, "status"]), 1500);
269 		}
270 	} else {
271 		dialog.popdown();
272 
273 		if (status == ZmZimletsPage.ZIMLET_UPLOAD_STATUS_FAIL) {
274 			this._handleZimletDeployError();
275 		}
276 		else {
277 			this._uploadButton.setEnabled(true);
278 
279 			var settings = appCtxt.getSettings(appCtxt.accountList.mainAccount);
280 			settings._showConfirmDialog(ZmMsg.zimletDeploySuccess, settings._refreshBrowserCallback.bind(settings), DwtMessageDialog.INFO_STYLE);
281 		}
282 	}
283 };
284 
285 ZmZimletsPage.prototype._handleZimletDeployError =
286 function() {
287 	this._uploadButton.setEnabled(true);
288 
289 	var dialog = appCtxt.getMsgDialog();
290 	dialog.setMessage(ZmMsg.zimletDeployError, DwtMessageDialog.CRITICAL_STYLE);
291 	dialog.popup();
292 };
293 
294 ZmZimletsPage.prototype._handleZimletCancel =
295 function(dialog) {
296 	dialog.popdown();
297 	this._uploadButton.setEnabled(true);
298 };
299 
300 ZmZimletsPage.prototype.undeployZimlet =
301 function(zimletName) {
302 	var callback = new AjxCallback(this, this._handleUndeployZimlet, [zimletName]);
303 	this._getAdminAuth(callback);
304 };
305 
306 ZmZimletsPage.prototype._handleUndeployZimlet =
307 function(zimletName) {
308 	var soapDoc = AjxSoapDoc.create("UndeployZimletRequest", "urn:zimbraAdmin");
309 	soapDoc.getMethod().setAttribute("name", zimletName);
310 
311 	var params = {
312 		soapDoc: soapDoc,
313 		asyncMode: true,
314 		skipAuthCheck: true,
315 		callback: (new AjxCallback(this, this._undeployZimletResponse, [zimletName])),
316 		serverUri: ZmZimletsPage.ADMIN_URI,
317 		authToken: this._adminAuthToken
318 	};
319 
320 	(new ZmCsfeCommand()).invoke(params);
321 };
322 
323 ZmZimletsPage.prototype._undeployZimletResponse =
324 function(zimletName, result) {
325 
326     // remove admin auth key
327     AjxCookie.deleteCookie(document, ZmZimletsPage.ADMIN_COOKIE_NAME, "/service/upload");
328     
329 	if (result.isException()) {
330 		this._controller.popupErrorDialog(ZmMsg.zimletUndeployError, result.getException());
331 		return;
332 	}
333 
334 	// remove the uninstalled zimlet from the listview
335     var zimletsCtxt = this.getZimletsCtxt();
336 	var zimlet = zimletsCtxt.getPrefZimletByName(zimletName);
337 	if (zimlet) {
338 		zimletsCtxt.removePrefZimlet(zimlet);
339 		this._listView.set(zimletsCtxt.getZimlets().clone());
340 	}
341 
342 	// prompt user to restart client
343 	var settings = appCtxt.getSettings(appCtxt.accountList.mainAccount);
344 	settings._showConfirmDialog(ZmMsg.zimletUndeploySuccess, settings._refreshBrowserCallback.bind(settings), DwtMessageDialog.INFO_STYLE);
345 };
346 
347 ZmZimletsPage.prototype.addCommand  =
348 function(batchCommand) {
349 	var soapDoc = AjxSoapDoc.create("ModifyZimletPrefsRequest", "urn:zimbraAccount");
350 	// LDAP supports multi-valued attrs, so don't serialize list
351 	var zimlets = this.getZimletsArray();
352 	var settingsObj = appCtxt.getSettings();
353 	var setting = settingsObj.getSetting(ZmSetting.CHECKED_ZIMLETS);
354 	var checked = [];
355 	for (var i = 0; i < zimlets.length; i++) {
356 		if (zimlets[i].active) {
357 			checked.push(zimlets[i].name);
358 		}
359 		var node = soapDoc.set("zimlet", null);
360 		node.setAttribute("name", zimlets[i].name);
361 		node.setAttribute("presence", (zimlets[i].active ? "enabled" : "disabled"));
362 	}
363 	setting.setValue(checked);
364 	batchCommand.addNewRequestParams(soapDoc);
365 };
366 
367 ZmZimletsPage.prototype._reloadZimlets =
368 function() {
369 	// reset all zimlets origStatus
370 	var zimlets = this.getZimletsArray();
371 	for (var i = 0; i < zimlets.length; i++) {
372 		zimlets[i].resetStatus();
373 	}
374 };
375 
376 ZmZimletsPage.prototype.getPostSaveCallback =
377 function() {
378 	return new AjxCallback(this, this._postSave);
379 };
380 ZmZimletsPage.prototype._postSave =
381 function() {
382 	if (!this.isDirty()) { return; }
383 
384 	this._reloadZimlets();
385 
386 	var settings = appCtxt.getSettings();
387 	settings._showConfirmDialog(ZmMsg.zimletChangeRestart, settings._refreshBrowserCallback.bind(settings));
388 };
389 
390 ZmZimletsPage.prototype._isChecked =
391 function(name) {
392 	var z = this.getZimletsCtxt().getPrefZimletByName(name);
393 	return (z && z.active);
394 };
395 
396 ZmZimletsPage.prototype.isDirty =
397 function() {
398 	var dirty = false;
399 	var arr = this.getZimletsArray();
400 	var dirtyZimlets = [];
401 
402 	var printZimlet = function(zimlet) {
403 		if (AjxUtil.isArray(zimlet)) {
404 			return AjxUtil.map(zimlet, printZimlet).join("\n");
405 		}
406 		return [zimlet.name," (from ",zimlet._origStatus," to ",zimlet.active,")"].join("");
407 	}
408 
409 	for (var i = 0; i < arr.length; i++) {
410 		if (arr[i]._origStatus != arr[i].active) {
411 			dirty = true;
412 			dirtyZimlets.push(arr[i]);
413 		}
414 	}
415 
416 	if (dirty) {
417 		AjxDebug.println(AjxDebug.PREFS, "Dirty preferences:\n" + "Dirty zimlets:\n" + printZimlet(dirtyZimlets));
418 	}
419 	return dirty;
420 };
421 
422 /**
423  * Gets the zimlet preferences.
424  * 
425  * @return	{ZmPrefZimlets}	the zimlets
426  * 
427  * @private
428  */
429 ZmZimletsPage.prototype.getZimletsCtxt =
430 function() {
431 	if (!this._zimlets) {
432 		this._zimlets = ZmZimletsPage._getZimlets();
433 	}
434 	return this._zimlets;
435 };
436 
437 ZmZimletsPage.prototype.getZimlets =
438 function() {
439 	return this.getZimletsCtxt().getZimlets();
440 };
441 
442 ZmZimletsPage.prototype.getZimletsArray =
443 function() {
444 	return this.getZimlets().getArray();
445 };
446 
447 
448 ZmZimletsPage._getZimlets =
449 function() {
450 	var allz = appCtxt.get(ZmSetting.ZIMLETS) || [];
451 	var zimlets = new ZmPrefZimlets();
452     var zimletsLoaded = appCtxt.getZimletMgr().isLoaded();
453 	for (var i = 0; i <  allz.length; i++) {
454 		var name = allz[i].zimlet[0].name;
455 		if (allz[i].zimletContext[0].presence == "mandatory") {
456 			continue; // skip mandatory zimlets to be shown in prefs
457 		}
458 		var desc = allz[i].zimlet[0].description;
459 		var label = allz[i].zimlet[0].label || name.replace(/^.*_/,"");
460         if (zimletsLoaded) {
461             desc = ZmZimletContext.processMessage(name, desc);
462             label = ZmZimletContext.processMessage(name, label);
463         }
464 		var isEnabled = allz[i].zimletContext[0].presence == "enabled";
465 		zimlets.addPrefZimlet(new ZmPrefZimlet(name, isEnabled, desc, label));
466 	}
467 	zimlets.sortByName();
468 	return zimlets;
469 };
470 
471 /**
472  * ZmPrefZimletListView
473  *
474  * @param parent
475  * @param controller
476  * @private
477  */
478 ZmPrefZimletListView = function(parent, controller) {
479 	DwtListView.call(this, {
480 		parent: parent,
481 		className: "ZmPrefZimletListView",
482 		headerList: this._getHeaderList(),
483         id: "ZmPrefZimletListView",
484 		view: ZmId.VIEW_PREF_ZIMLETS
485 	});
486 
487 	this._controller = controller;
488 	this.setMultiSelect(false); // single selection only
489 	this._internalId = AjxCore.assignId(this);
490 };
491 
492 ZmPrefZimletListView.COL_ACTIVE	= "ac";
493 ZmPrefZimletListView.COL_NAME	= "na";
494 ZmPrefZimletListView.COL_DESC	= "ds";
495 ZmPrefZimletListView.COL_ACTION	= "an";
496 
497 ZmPrefZimletListView.prototype = new DwtListView;
498 ZmPrefZimletListView.prototype.constructor = ZmPrefZimletListView;
499 
500 ZmPrefZimletListView.prototype.toString =
501 function() {
502 	return "ZmPrefZimletListView";
503 };
504 
505 /**                                                                        
506  * Only show zimlets that have at least one valid action (eg, if the only action
507  * is "tag" and tagging is disabled, don't show the rule).
508  */
509 ZmPrefZimletListView.prototype.set =
510 function(list) {
511 	this._checkboxIds = [];
512     this._zimletsLoaded = appCtxt.getZimletMgr().isLoaded();
513 	DwtListView.prototype.set.call(this, list);
514     if (!this._zimletsLoaded) {
515         appCtxt.addZimletsLoadedListener(new AjxListener(this, this._handleZimletsLoaded));
516     }
517 };
518 
519 ZmPrefZimletListView.prototype._handleZimletsLoaded = function(evt) {
520     this._zimletsLoaded = true;
521     var array = this.parent.getZimletsArray();
522     for (var i = 0; i < array.length; i++) {
523         var item = array[i];
524         var label = item.label || item.name.replace(/^.*_/,"");
525         item.label = ZmZimletContext.processMessage(item.name, label);
526         item.desc = ZmZimletContext.processMessage(item.name, item.desc);
527         this.setCellContents(item, ZmPrefZimletListView.COL_NAME, AjxStringUtil.htmlEncode(item.label));
528         this.setCellContents(item, ZmPrefZimletListView.COL_DESC, AjxStringUtil.htmlEncode(item.desc));
529     }
530 };
531 
532 ZmPrefZimletListView.prototype._getRowId =
533 function(item, params) {
534     if (item) {
535         var rowIndex = this._list && this._list.indexOf(item);
536         if (rowIndex && rowIndex != -1)
537             return "ZmPrefZimletListView_row_" + rowIndex;
538     }
539 
540     return null;
541 };
542 
543 
544 ZmPrefZimletListView.prototype.setCellContents = function(item, field, html) {
545 	var el = this.getCellElement(item, field);
546 	if (!el) { return; }
547 	el.innerHTML = html;
548 };
549 
550 ZmPrefZimletListView.prototype.getCellElement = function(item, field) {
551 	return document.getElementById(this._getCellId(item, field));
552 };
553 
554 ZmPrefZimletListView.prototype._getCellId = function(item, field, params) {
555 	return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_CELL, "zimlets", item.name, field);
556 };
557 
558 ZmPrefZimletListView.prototype._getHeaderList =
559 function() {
560 	var hlist = [
561 		(new DwtListHeaderItem({field:ZmPrefZimletListView.COL_ACTIVE, text:ZmMsg.active, width:ZmMsg.COLUMN_WIDTH_ACTIVE})),
562         (new DwtListHeaderItem({field:ZmPrefZimletListView.COL_NAME, text:ZmMsg.name, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV})),    
563 		(new DwtListHeaderItem({field:ZmPrefZimletListView.COL_DESC, text:ZmMsg.description}))
564 	];
565 
566 	if (appCtxt.isOffline) {
567 		hlist.push(new DwtListHeaderItem({field:ZmPrefZimletListView.COL_ACTION, text:ZmMsg.action, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV}));
568 	}
569 
570 	return hlist;
571 };
572 
573 ZmPrefZimletListView.prototype._getCellContents =
574 function(html, idx, item, field, colIdx, params) {
575 	if (field == ZmPrefZimletListView.COL_ACTIVE) {
576 		html[idx++] = "<input name='checked_zimlets' type='checkbox' ";
577 		html[idx++] = item.active ? "checked " : "";
578 		html[idx++] = "id='";
579 		html[idx++] = item.name;
580 		html[idx++] = "_zimletCheckbox' _name='";
581 		html[idx++] = item.name;
582 		html[idx++] = "' _flvId='";
583 		html[idx++] = this._internalId;
584 		html[idx++] = "' onchange='ZmPrefZimletListView._activeStateChange'>";
585 	}
586 	else if (field == ZmPrefZimletListView.COL_DESC) {
587         var desc = this._zimletsLoaded ? item.desc : ZmMsg.loading;
588         html[idx++] = "<div id='";
589         html[idx++] = this._getCellId(item, ZmPrefZimletListView.COL_DESC);
590         html[idx++] = "'>";
591 		html[idx++] = AjxStringUtil.stripTags(desc, true);
592         html[idx++] = "</div>";
593 	}
594 	else if (field == ZmPrefZimletListView.COL_NAME) {
595         html[idx++] = "<div id='";
596         html[idx++] = this._getCellId(item, ZmPrefZimletListView.COL_NAME);
597         html[idx++] = "' title='";
598         html[idx++] = item.name;
599         html[idx++] = "'>";
600 		html[idx++] = AjxStringUtil.stripTags(item.getNameWithoutPrefix(!this._zimletsLoaded), true);
601         html[idx++] = "</div>";
602 	}
603 	else if (field == ZmPrefZimletListView.COL_ACTION) {
604 		html[idx++] = "<a href='javascript:;' onclick='ZmPrefZimletListView.undeployZimlet(";
605 		html[idx++] = '"' + item.name + '"';
606 		html[idx++] = ");'>";
607 		html[idx++] = ZmMsg.uninstall;
608 		html[idx++] = "</a>";
609 	}
610 
611 	return idx;
612 };
613 
614 ZmPrefZimletListView.undeployZimlet =
615 function(zimletName) {
616 	appCtxt.getCurrentView().prefView["PREF_ZIMLETS"].undeployZimlet(zimletName);
617 };
618 
619 /**
620 * Handles click of 'active' checkbox by toggling the rule's active state.
621 *
622 * @param ev			[DwtEvent]	click event
623 */
624 ZmPrefZimletListView._activeStateChange =
625 function(ev) {
626 	var target = DwtUiEvent.getTarget(ev);
627 	var flvId = target.getAttribute("_flvId");
628 	var flv = AjxCore.objectWithId(flvId);
629 	var name = target.getAttribute("_name");
630 	var z = flv.parent.getZimletsCtxt().getPrefZimletByName(name);
631 	if (z) {
632 		z.active = !z.active;
633 	}
634 };
635 
636 /**
637 * Override so that we don't change selection when the 'active' checkbox is clicked.
638 * Also contains a hack for IE for handling a click of the 'active' checkbox, because
639 * the ONCHANGE handler was only getting invoked on every other checkbox click for IE.
640 *
641 * @param clickedEl	[Element]	list DIV that received the click
642 * @param ev			[DwtEvent]	click event
643 * @param button		[constant]	button that was clicked
644 */
645 ZmPrefZimletListView.prototype._allowLeftSelection =
646 function(clickedEl, ev, button) {
647 	var target = DwtUiEvent.getTarget(ev);
648 	var isInput = (target.id.indexOf("_zimletCheckbox") > 0);
649 	if (isInput) {
650 		ZmPrefZimletListView._activeStateChange(ev);
651 	}
652 
653 	return !isInput;
654 };
655 
656 /**
657  * Model class to hold the list of PrefZimlets
658  * @private
659  */
660 ZmPrefZimlets = function() {
661    ZmModel.call(this, ZmEvent.S_PREF_ZIMLET);
662    this._vector = new AjxVector();
663    this._zNameHash = {};
664 };
665 
666 ZmPrefZimlets.prototype = new ZmModel;
667 ZmPrefZimlets.prototype.constructor = ZmPrefZimlets;
668 
669 ZmPrefZimlets.prototype.toString =
670 function() {
671 	return "ZmPrefZimlets";
672 };
673 
674 ZmPrefZimlets.prototype.getZimlets =
675 function() {
676 	return this._vector;
677 };
678 
679 ZmPrefZimlets.prototype.addPrefZimlet =
680 function(zimlet) {
681 	this._vector.add(zimlet);
682 	this._zNameHash[zimlet.name] = zimlet;
683 };
684 
685 ZmPrefZimlets.prototype.removePrefZimlet =
686 function(zimlet) {
687 	delete this._zNameHash[zimlet.name];
688 	this._vector.remove(zimlet);
689 };
690 
691 ZmPrefZimlets.prototype.getPrefZimletByName =
692 function(name) {
693    return this._zNameHash[name];
694 };
695 
696 /**
697  *
698  * @param desc true for desc sorting, false or empty otherwise
699  */
700 ZmPrefZimlets.prototype.sortByName =
701 function(desc) {
702 	var r = 0;
703     var zimletsLoaded = appCtxt.getZimletMgr().isLoaded();
704 	this._vector.sort(function(a,b) {
705 		var aname = a.getNameWithoutPrefix(!zimletsLoaded).toLowerCase();
706 		var bname = b.getNameWithoutPrefix(!zimletsLoaded).toLowerCase();
707 
708 		if (aname == bname) {
709 			r = 0;
710 		} else if (aname > bname) {
711 			r = 1;
712 		} else {
713 			r = -1;
714 		}
715 		return (desc ? -r : r);
716 	});
717 };
718 
719 /**
720  * ZmPrefZimlet
721  *
722  * @param name
723  * @param active
724  * @param desc
725  *
726  * @private
727  */
728 ZmPrefZimlet = function(name, active, desc, label) {
729 	this.name = name;
730 	this.active = (active !== false);
731 	this.desc = desc;
732 	this.label = label;
733 	this._origStatus = this.active;
734 };
735 
736 ZmPrefZimlet.prototype.getNameWithoutPrefix	=
737 function(noLabel) {
738 	if (!noLabel && this.label != null && this.label.length > 0) {
739 		return	this.label;
740 	}
741 
742 	return this.name.substring(this.name.lastIndexOf("_")+1);
743 };
744 
745 ZmPrefZimlet.prototype.resetStatus =
746 function() {
747 	this._origStatus = this.active;
748 };
749 
750 ZmPrefZimlet.prototype.restoreStatus =
751 function() {
752 	this.active = this._origStatus;
753 };
754