1 /*
  2  * ***** BEGIN LICENSE BLOCK *****
  3  * Zimbra Collaboration Suite Web Client
  4  * Copyright (C) 2011, 2013, 2014, 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) 2011, 2013, 2014, 2016 Synacor, Inc. All Rights Reserved.
 21  * ***** END LICENSE BLOCK *****
 22  */
 23 
 24 /**
 25  * Creates a Back-up preference page.
 26  * @constructor
 27  * @class ZmBackupPage
 28  * This class represents a page that allows the user to set preferences on backup
 29  * For Instance, How often user wants to backup data and list of accounts user wants to back-up
 30  *
 31  * @author KK
 32  *
 33  * @param {DwtControl}	parent			the containing widget
 34  * @param {Object}	section			the page
 35  * @param {ZmPrefController}	controller		the prefs controller
 36  *
 37  * @extends		ZmPreferencesPage
 38  * @private
 39  */
 40 ZmBackupPage = function(parent, section, controller) {
 41     ZmPreferencesPage.call(this, parent, section, controller);
 42     //this.backupDetailsLoaded = false;
 43 };
 44 
 45 ZmBackupPage.prototype = new ZmPreferencesPage;
 46 ZmBackupPage.prototype.constructor = ZmBackupPage;
 47 
 48 ZmBackupPage.prototype.toString =
 49 function () {
 50     return "ZmBackupPage";
 51 };
 52 
 53 ZmBackupPage.prototype.showMe =
 54 function(){
 55     ZmPreferencesPage.prototype.showMe.call(this);
 56     if (this._acctListView) {
 57         var s = this._acctListView.getSelection();
 58         this._acctListView.set(this.getAccounts()._vector.clone());
 59         if (s && s[0]) {
 60             this._acctListView.setSelection(s[0]);
 61         }
 62     }
 63 
 64     if (this._restoreListView) {
 65         var r = this._restoreListView.getSelection();
 66         this._restoreListView.set(this.getBackups(true)._vector.clone());
 67         if (r && r[0]) {
 68             this._restoreListView.setSelection(r[0]);
 69         }
 70     }
 71 };
 72 
 73 
 74 /**
 75  * @private
 76  */
 77 ZmBackupPage.prototype._setupCustom =
 78 function(id, setup, value) {
 79 
 80     if (id == ZmSetting.OFFLINE_BACKUP_ACCOUNT_ID) {
 81         this._acctListView = new ZmPrefAcctListView(this, this._controller);
 82         return this._acctListView;
 83     }
 84 
 85     if (id == ZmSetting.OFFLINE_BACKUP_RESTORE) {
 86         this._restoreListView = new ZmPrefBackupListView(this, this._controller);
 87         return this._restoreListView;
 88     }
 89 
 90     return ZmPreferencesPage.prototype._setupCustom.apply(this, arguments);
 91 };
 92 
 93 ZmBackupPage.prototype._createControls =
 94 function() {
 95 
 96     // add "Backup" button
 97     this._backupButton = new DwtButton({parent:this, parentElement: this._htmlElId+"_button"});
 98     this._backupButton.setText(ZmMsg.offlineBackUpButton);
 99     this._backupButton.addSelectionListener(new AjxListener(this, this._handleBackupAccountsButton));
100 
101     this._restoreButton = new DwtButton({parent:this, parentElement: this._htmlElId+"_restore_button"});
102     this._restoreButton.setText(ZmMsg.offlineBackUpRestore);
103     this._restoreButton.addSelectionListener(new AjxListener(this, this._handleRestoreBackupButton));
104 
105     ZmPreferencesPage.prototype._createControls.apply(this, arguments);
106 };
107 
108 ZmBackupPage.prototype._handleBackupAccountsButton =
109 function() {
110     this._backupButton.setEnabled(false);
111 
112     var accts = this.getAccounts()._vector.getArray();
113 
114     var settingsObj = appCtxt.getSettings();
115     var setting = settingsObj.getSetting(ZmSetting.OFFLINE_BACKUP_ACCOUNT_ID);
116 
117     var checked = [];
118     for (var i = 0; i < accts.length; i++) {
119         if (accts[i].active) {
120             checked.push(accts[i].id);
121         }
122     }
123     if(checked.length < 1) {
124         this._backupButton.setEnabled(true);
125         return;
126     }
127 
128     setting.setValue(checked);
129 
130     var soapDoc = AjxSoapDoc.create("BatchRequest", "urn:zimbra", null);
131     soapDoc.setMethodAttribute("onerror", "continue");
132     for (var k=0; k<checked.length; k++) {
133         var requestNode = soapDoc.set("AccountBackupRequest",null,null,"urn:zimbraOffline");
134         requestNode.setAttribute("id", checked[k]);
135     }
136 
137     var params = {
138         soapDoc: soapDoc,
139         asyncMode: true,
140         errorCallback: null,
141         accountName: appCtxt.accountList.mainAccount.name
142     };
143 
144     params.callback = new AjxCallback(this, this._handleBackupStarted);
145     var appController = appCtxt.getAppController();
146     appController.sendRequest(params);
147 };
148 
149 ZmBackupPage._handleBackupNowLink =
150 function(id, obj) {
151     var backupPage = DwtControl.fromElementId(obj);
152     backupPage._handleBackupNowLink(id);
153 };
154 
155 ZmBackupPage.prototype._handleBackupNowLink =
156 function(id) {
157 
158     try{
159         var soapDoc = AjxSoapDoc.create("AccountBackupRequest", "urn:zimbraOffline");
160         var method = soapDoc.getMethod();
161         method.setAttribute("id", id);
162         var respCallback = new AjxCallback(this, this._handleBackupAcctStarted , id);
163         var params = {
164             soapDoc:soapDoc,
165             callback:respCallback,
166             asyncMode:true
167         };
168         appCtxt.getAppController().sendRequest(params);
169     }
170     finally { // do
171         return false;
172     }
173 };
174 
175 ZmBackupPage.prototype._handleBackupAcctStarted =
176 function(id) {
177     appCtxt.setStatusMsg(AjxMessageFormat.format(ZmMsg.offlineBackupStartedForAcct, appCtxt.accountList.getAccount(id).getDisplayName()));
178 };
179 
180 ZmBackupPage.prototype._handleBackupStarted =
181 function(result) {
182     appCtxt.setStatusMsg(ZmMsg.offlineBackUpStarted);
183     this._backupButton.setEnabled(true);
184 };
185 
186 ZmBackupPage.prototype._handleRestoreBackupButton =
187 function() {
188 
189     this._restoreButton.setEnabled(false);
190 
191     var backups = this.getBackups()._vector.getArray();
192     var sel = [];
193     for (var i = 0; i < backups.length; i++) {
194         if (backups[i].active) {
195             var bk = backups[i];
196             sel.push(backups[i]);
197             break;
198         }
199     }
200 
201     if(sel.length < 1) {
202         this._restoreButton.setEnabled(true);
203         return;
204     }
205 
206     var soapDoc = AjxSoapDoc.create("AccountRestoreRequest", "urn:zimbraOffline");
207     var method = soapDoc.getMethod();
208     method.setAttribute("id", sel[0].acct);
209     method.setAttribute("time", sel[0].timestamp);
210     var respCallback = new AjxCallback(this, this._handleAccountRestoreSent, [sel[0].acct]);
211     var params = {
212         soapDoc:soapDoc,
213         callback:respCallback,
214         asyncMode:true
215     };
216     appCtxt.getAppController().sendRequest(params);
217 
218 };
219 
220 ZmBackupPage.prototype._handleAccountRestoreSent =
221 function(id, resp) {
222 
223     this._restoreButton.setEnabled(true);
224     if(resp._data.AccountRestoreResponse.status == "restored") {
225         appCtxt.setStatusMsg(AjxMessageFormat.format(ZmMsg.offlineBackupRestored, appCtxt.accountList.getAccount(id).name));
226     }
227 };
228 
229 ZmBackupPage.prototype.addCommand  =
230 function(batchCommand) {
231 
232     var soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount");
233     var accts = this.getAccounts()._vector.getArray();
234 
235     var settingsObj = appCtxt.getSettings();
236     var setting = settingsObj.getSetting(ZmSetting.OFFLINE_BACKUP_ACCOUNT_ID);
237 
238     var checked = [];
239     for (var i = 0; i < accts.length; i++) {
240         if (accts[i].active) {
241             checked.push(accts[i].id);
242         }
243     }
244     var node = soapDoc.set("pref", checked.join(","));
245     node.setAttribute("name", "zimbraPrefOfflineBackupAccountId");
246     setting.setValue(checked.join(", "));
247     batchCommand.addNewRequestParams(soapDoc);
248 };
249 
250 /**
251  * Gets the account preferences.
252  *
253  * @return	{ZmPrefAccounts}	the accounts
254  *
255  * @private
256  */
257 
258 
259 ZmBackupPage.prototype.getAccounts =
260 function() {
261     if (!this._accounts) {
262         this._accounts = ZmBackupPage._getAccounts();
263     }
264     return this._accounts;
265 };
266 
267 
268 ZmBackupPage._getAccounts =
269 function() {
270     var savedAccounts = appCtxt.get(ZmSetting.OFFLINE_BACKUP_ACCOUNT_ID);
271     savedAccounts = (savedAccounts && savedAccounts.length) ? savedAccounts.split(",") : [];
272     var accounts = new ZmPrefAccounts();
273     var visAccts = appCtxt.accountList.visibleAccounts;
274     for (var i=0; i< visAccts.length; i++) {
275         var name =  visAccts[i].getDisplayName();
276         var desc = visAccts[i].name;
277         var id = visAccts[i].id;
278 
279         loop1: for (var k=0; k < savedAccounts.length ; k++)  {
280                 var checked = (id == AjxStringUtil.trim(savedAccounts[k]));
281                 if(checked) { break loop1; }
282             }
283         accounts.addPrefAccount(new ZmPrefAccount(name, checked, desc, id));
284     }
285     return accounts;
286 };
287 
288 ZmBackupPage.prototype._isChecked =
289 function(name) {
290     var z = this.getAccounts().getPrefAccountByName(name);
291     return (z && z.active);
292 };
293 
294 ZmBackupPage.prototype.isDirty =
295 function() {
296     var allAccountss = this.getAccounts();
297     var r = false;
298     var arr = allAccountss._vector.getArray();
299     for (var i = 0; i < arr.length; i++) {
300         if (arr[i]._origStatus != arr[i].active) {
301             r = true;
302             break;
303         }
304     }
305     return r;
306 };
307 
308 
309 /**
310  * ZmPrefAcctListView
311  *
312  * @param parent
313  * @param controller
314  * @private
315  */
316 ZmPrefAcctListView = function(parent, controller) {
317 
318     DwtListView.call(this, {
319         parent: parent,
320         className: "ZmFilterListView",
321         headerList: this._getHeaderList(),
322         view: ZmId.OFFLINE_BACKUP_ACCOUNT_ID
323     });
324 
325     this._controller = controller;
326     this.setMultiSelect(false); // single selection only
327     this._internalId = AjxCore.assignId(this);
328 };
329 
330 ZmPrefAcctListView.COL_ACTIVE	= "ac";
331 ZmPrefAcctListView.COL_NAME	= "na";
332 ZmPrefAcctListView.COL_DESC	= "ds";
333 ZmPrefAcctListView.COL_ACTION	= "an";
334 ZmPrefAcctListView.CHECKBOX_PREFIX = "_accCheckbox";
335 
336 
337 ZmPrefAcctListView.prototype = new DwtListView;
338 ZmPrefAcctListView.prototype.constructor = ZmPrefAcctListView;
339 
340 ZmPrefAcctListView.prototype.toString =
341 function() {
342     return "ZmPrefAcctListView";
343 };
344 
345 ZmPrefAcctListView.prototype.set =
346 function(list) {
347     this._checkboxIds = [];
348     DwtListView.prototype.set.call(this, list);
349     this._handleAccts();
350 };
351 
352 ZmPrefAcctListView.prototype._handleAccts = function(evt) {
353     this._acctsLoaded = true;
354     var item = {};
355     var visAccts = this.parent.getAccounts()._vector.getArray();
356     for (var i=0; i< visAccts.length; i++) {
357         item.name =  visAccts[i].name;
358         item.desc = visAccts[i].desc;
359         this.setCellContents(item, ZmPrefAcctListView.COL_NAME, AjxStringUtil.htmlEncode(AjxStringUtil.trim(item.name)));
360         this.setCellContents(item, ZmPrefAcctListView.COL_DESC, AjxStringUtil.htmlEncode(item.desc));
361     }
362 };
363 
364 ZmPrefAcctListView.prototype.setCellContents = function(item, field, html) {
365     var el = this.getCellElement(item, field);
366     if (!el) { return; }
367     el.innerHTML = html;
368 };
369 
370 ZmPrefAcctListView.prototype.getCellElement = function(item, field) {
371     return document.getElementById(this._getCellId(item, field));
372 };
373 
374 ZmPrefAcctListView.prototype._getCellId = function(item, field, params) {
375     return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_CELL, "accts", AjxStringUtil.trim(item.name), field);
376 };
377 
378 ZmPrefAcctListView.prototype._getHeaderList =
379 function() {
380     return [
381         (new DwtListHeaderItem({field:ZmPrefAcctListView.COL_ACTIVE, text:ZmMsg.active, width:ZmMsg.COLUMN_WIDTH_ACTIVE})),
382         (new DwtListHeaderItem({field:ZmPrefAcctListView.COL_NAME, text:ZmMsg.name, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV})),
383         (new DwtListHeaderItem({field:ZmPrefAcctListView.COL_DESC, text:ZmMsg.emailAddr, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV})),
384         (new DwtListHeaderItem({field:ZmPrefAcctListView.COL_ACTION, text:ZmMsg.action, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV}))
385     ];
386 
387 };
388 
389 ZmPrefAcctListView.prototype._getCellClass =
390 function(item, field, params) {
391     if (field == ZmPrefAcctListView.COL_ACTIVE) {
392         return "FilterActiveCell";
393     }
394     return DwtListView.prototype._getCellClass.call(this, item, field, params);
395 };
396 
397 ZmPrefAcctListView.prototype._getCellContents =
398 function(html, idx, item, field, colIdx, params) {
399     if (field == ZmPrefAcctListView.COL_ACTIVE) {
400         html[idx++] = "<input name='OFFLINE_BACKUP_ACCOUNT_ID' type='checkbox' ";
401         html[idx++] = item.active ? "checked " : "";
402         html[idx++] = "id='";
403         html[idx++] = AjxStringUtil.trim(item.name);
404         html[idx++] = "_acctsCheckbox' _name='";
405         html[idx++] = AjxStringUtil.trim(item.name);
406         html[idx++] = "' _acId='";
407         html[idx++] = this._internalId;
408         html[idx++] = "' onclick='ZmPrefAcctListView._activeStateChange;'>";
409     } else if (field == ZmPrefAcctListView.COL_DESC) {
410         html[idx++] = "<div id='";
411         html[idx++] = this._getCellId(item, ZmPrefAcctListView.COL_DESC);
412         html[idx++] = "'>";
413         html[idx++] = AjxStringUtil.stripTags(item.desc, true);
414         html[idx++] = "</div>";
415     } else if (field == ZmPrefAcctListView.COL_NAME) {
416         html[idx++] = "<div id='";
417         html[idx++] = this._getCellId(item, ZmPrefAcctListView.COL_NAME);
418         html[idx++] = "' title='";
419         html[idx++] = item.name;
420         html[idx++] = "'>";
421         html[idx++] = AjxStringUtil.stripTags(item.name, true);
422         html[idx++] = "</div>";
423     } else if (field == ZmPrefAcctListView.COL_ACTION) {
424         html[idx++] = "<a href='javascript:;' onclick='ZmBackupPage._handleBackupNowLink(";
425         var accId = appCtxt.accountList.getAccountByName(AjxStringUtil.trim(item.desc)).id;
426         html[idx++] = '"' + accId.toString();
427         html[idx++] = '","' + this.parent._htmlElId + '"';
428         html[idx++] = ");'>";
429         html[idx++] = ZmMsg.offlineBackUpNow;
430         html[idx++] = "</a>";
431     }
432 
433     return idx;
434 };
435 
436 
437 /**
438  * Handles click of 'active' checkbox by toggling the rule's active state.
439  *
440  * @param ev			[DwtEvent]	click event
441  */
442 ZmPrefAcctListView._activeStateChange =
443 function(ev) {
444     var target = DwtUiEvent.getTarget(ev);
445     var flvId = target.getAttribute("_acId");
446     var flv = AjxCore.objectWithId(flvId);
447     var name = target.getAttribute("_name");
448     var z = flv.parent.getAccounts().getPrefAccountByName(name);
449     if (z) {
450         z.active = !z.active;
451     }
452 };
453 
454 ZmPrefAcctListView.prototype._allowLeftSelection =
455 function(clickedEl, ev, button) {
456     var target = DwtUiEvent.getTarget(ev);
457     var isInput = (target.id.indexOf("_acctsCheckbox") > 0);
458     if (isInput) {
459         ZmPrefAcctListView._activeStateChange(ev);
460     }
461 
462     return !isInput;
463 };
464 
465 /**
466  * Model class to hold the list of PrefAccounts
467  * @private
468  */
469 ZmPrefAccounts = function() {
470     ZmModel.call(this, ZmEvent.S_PREF_ACCOUNT);
471     this._vector = new AjxVector();
472     this._zNameHash = {};
473 };
474 
475 ZmPrefAccounts.prototype = new ZmModel;
476 ZmPrefAccounts.prototype.constructor = ZmPrefAccounts;
477 
478 ZmPrefAccounts.prototype.toString =
479 function() {
480     return "ZmPrefAccounts";
481 };
482 
483 ZmPrefAccounts.prototype.addPrefAccount =
484 function(Account) {
485     this._vector.add(Account);
486     this._zNameHash[Account.name] = Account;
487 };
488 
489 ZmPrefAccounts.prototype.removePrefAccount =
490 function(Account) {
491     delete this._zNameHash[Account.name];
492     this._vector.remove(Account);
493 };
494 
495 ZmPrefAccounts.prototype.getPrefAccountByName =
496 function(name) {
497     return this._zNameHash[name];
498 };
499 
500 /**
501  * ZmPrefAccount
502  *
503  * @param name
504  * @param active
505  * @param desc
506  *
507  * @private
508  */
509 ZmPrefAccount = function(name, active, desc, id) {
510     this.name = name;
511     this.active = (active !== false);
512     this.desc = desc;
513     this.id = id;
514     this._origStatus = this.active;
515 };
516 
517 ZmBackupPage.prototype.getBackups =
518 function(force) {
519     if (!this._backups || force) {
520         this._backups = ZmBackupPage._getBackups();
521     }
522     return this._backups;
523 };
524 
525 
526 ZmBackupPage._getBackups =
527 function(evt) {
528 
529     var soapDoc = AjxSoapDoc.create("AccountBackupEnumerationRequest", "urn:zimbraOffline");
530     var result = appCtxt.getAppController().sendRequest({soapDoc:soapDoc, asyncMode:false});
531 
532     var _restoreAccts = result.AccountBackupEnumerationResponse.account;
533     var backups = new ZmPrefBackups();
534     for (var i=0; i< _restoreAccts.length; i++) {
535         var acct =  _restoreAccts[i].id;
536         var backupArray = _restoreAccts[i].backup;
537         if(!backupArray) {
538             continue;
539         }
540 
541         for(var k=0; k<backupArray.length; k++) {
542             var fileSize = backupArray[k].fileSize;
543             var timestamp = backupArray[k].time;
544             backups.addPrefBackup(new ZmPrefBackup(timestamp, false, fileSize, acct));
545         }
546 
547     }
548     return backups;
549 };
550 
551 /**
552  * ZmPrefBackupListView
553  *
554  * @param parent
555  * @param controller
556  * @private
557  */
558 
559 ZmPrefBackupListView  = function(parent, controller) {
560 
561     DwtListView.call(this, {
562         parent: parent,
563         className: "ZmFilterListView",
564         headerList: this._getHeaderList(),
565         view: ZmId.OFFLINE_BACKUP_RESTORE
566     });
567 
568     this._controller = controller;
569     this.setMultiSelect(false); // single selection only
570     this._internalId = AjxCore.assignId(this);
571 };
572 
573 ZmPrefBackupListView.COL_ACTIVE	= "ac";
574 ZmPrefBackupListView.COL_NAME	= "na";
575 ZmPrefBackupListView.COL_DESC	= "ds";
576 ZmPrefBackupListView.COL_ACTION	= "an";
577 ZmPrefBackupListView.CHECKBOX_PREFIX = "_BackupCheckbox";
578 
579 
580 ZmPrefBackupListView.prototype = new DwtListView;
581 ZmPrefBackupListView.prototype.constructor = ZmPrefBackupListView;
582 
583 ZmPrefBackupListView.prototype.toString =
584 function() {
585     return "ZmPrefBackupListView";
586 };
587 
588 ZmPrefBackupListView.prototype.set =
589 function(list) {
590     this._checkboxIds = [];
591     DwtListView.prototype.set.call(this, list);
592     //this.loadBackupDetails();
593 };
594 
595 ZmPrefBackupListView.prototype.loadBackupDetails =
596 function() {
597     var restoreArr =   this.parent._backups._vector.getArray();
598     var item = {};
599     for (var i = 0; i < restoreArr.length; i++) {
600         item.timestamp = restoreArr[i].timestamp;
601         item.size = restoreArr[i].fileSize;
602         item.acct = restoreArr[i].acct;
603         this.setCellContents(item, ZmPrefBackupListView.COL_NAME, item.timestamp);
604         this.setCellContents(item, ZmPrefBackupListView.COL_DESC, item.size);
605     }
606 };
607 
608 ZmPrefBackupListView.prototype.setCellContents = function(item, field, html) {
609     var el = this.getCellElement(item, field);
610     if (!el) { return; }
611     el.innerHTML = html;
612 };
613 
614 ZmPrefBackupListView.prototype.getCellElement = function(item, field) {
615     return document.getElementById(this._getCellId(item, field));
616 };
617 
618 ZmPrefBackupListView.prototype._getCellId = function(item, field, params) {
619     return DwtId.getListViewItemId(DwtId.WIDGET_ITEM_CELL, "backup", item.timestamp, field);
620 };
621 
622 
623 ZmPrefBackupListView.prototype._getHeaderList =
624 function() {
625     return  [
626         (new DwtListHeaderItem({field:ZmPrefBackupListView.COL_ACTIVE, text:ZmMsg.active, width:ZmMsg.COLUMN_WIDTH_ACTIVE})),
627         (new DwtListHeaderItem({field:ZmPrefBackupListView.COL_NAME, text:ZmMsg.date, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV})),
628         (new DwtListHeaderItem({field:ZmPrefBackupListView.COL_DESC, text:ZmMsg.size, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV})),
629         (new DwtListHeaderItem({field:ZmPrefBackupListView.COL_ACTION, text:ZmMsg.account, width:ZmMsg.COLUMN_WIDTH_FOLDER_DLV}))
630     ];
631 };
632 
633 ZmPrefBackupListView.prototype._getCellContents =
634 function(html, idx, item, field, colIdx, params) {
635     if (field == ZmPrefBackupListView.COL_ACTIVE) {
636         html[idx++] = "<input name='offline_backup_restore' type='checkbox' ";
637         html[idx++] = item.active ? "checked " : "";
638         html[idx++] = "id='";
639         html[idx++] = item.timestamp;
640         html[idx++] = "_";
641         html[idx++] = item.acct;
642         html[idx++] = "_backupCheckbox' _name='";
643         html[idx++] = item.timestamp;
644         html[idx++] = "_";
645         html[idx++] = item.acct;
646         html[idx++] = "' _bkId='";
647         html[idx++] = this._internalId;
648         html[idx++] = "' onchange='ZmPrefBackupListView._activeStateChange'>";
649     } else if (field == ZmPrefBackupListView.COL_DESC) {
650         html[idx++] = "<div id='";
651         html[idx++] = this._getCellId(item, ZmPrefBackupListView.COL_DESC);
652         html[idx++] = "'>";
653         var fSize = item.fileSize;
654         var numFormater = AjxNumberFormat.getInstance();
655         if (fSize != null && fSize >= 0) {
656             if (fSize < 1024) { //" B";
657                 fSize = numFormater.format(fSize) + " "+ZmMsg.b;
658             }
659             else if (fSize < (1024*1024) ) { //" KB";
660                 fSize = numFormater.format(Math.round((fSize / 1024) * 10) / 10) + " "+ZmMsg.kb;
661             }
662             else { //" MB";
663                 fSize = numFormater.format(Math.round((fSize / (1024*1024)) * 10) / 10) + " "+ZmMsg.mb;
664             }
665         } else { fSize = 0+" "+ZmMsg.b; }
666         html[idx++] = fSize;
667         html[idx++] = "</div>";
668     }
669     else if (field == ZmPrefBackupListView.COL_NAME) {
670         html[idx++] = "<div id='";
671         html[idx++] = this._getCellId(item, ZmPrefBackupListView.COL_NAME);
672         html[idx++] = "'>";
673         html[idx++] = AjxDateFormat.getDateTimeInstance(AjxDateFormat.MEDIUM).format(new Date(item.timestamp));
674         html[idx++] = "</div>";
675     }
676     else if (field == ZmPrefBackupListView.COL_ACTION) {
677         html[idx++] = "<div id='";
678         html[idx++] = this._getCellId(item, ZmPrefBackupListView.COL_ACTION);
679         html[idx++] = "'>";
680         html[idx++] = appCtxt.accountList.getAccount(item.acct).name;
681         html[idx++] = "</div>";
682     }
683     return idx;
684 };
685 
686 ZmPrefBackupListView._activeStateChange =
687 function(ev) {
688     var target = DwtUiEvent.getTarget(ev);
689     var bkId = target.getAttribute("_bkId");
690     var bk = AjxCore.objectWithId(bkId);
691     var name = target.getAttribute("_name");
692     var bkups = bk.parent.getBackups();
693     var z = bkups.getPrefBackupByName(name);
694     var bkarray = bkups._vector.getArray();
695     // Make checkbox behave like radio button
696     for (var k=0; k<bkarray.length; k++) {
697         if(bkarray[k].active && (bkarray[k].timestamp != z.timestamp)) {
698             var y = document.getElementById([bkarray[k].timestamp, "_", bkarray[k].acct, "_backupCheckbox"].join(""));
699             y.checked = false;
700             bkarray[k].active = false;
701         }
702     }
703     if (z) {
704         z.active = !z.active;
705     }
706 };
707 
708 ZmPrefBackupListView.prototype._allowLeftSelection =
709 function(clickedEl, ev, button) {
710     var target = DwtUiEvent.getTarget(ev);
711     var isInput = (target.id.indexOf("_backupCheckbox") > 0);
712     if (isInput) {
713         ZmPrefBackupListView._activeStateChange(ev);
714     }
715 
716     return !isInput;
717 };
718 
719 
720 /**
721  * Model class to hold the list of backups
722  * @private
723  */
724 ZmPrefBackups = function() {
725     ZmModel.call(this, ZmEvent.S_PREF_BACKUP);
726     this._vector = new AjxVector();
727     this._zNameHash = {};
728 };
729 
730 ZmPrefBackups.prototype = new ZmModel;
731 ZmPrefBackups.prototype.constructor = ZmPrefBackups;
732 
733 ZmPrefBackups.prototype.toString =
734 function() {
735     return "ZmPrefBackups";
736 };
737 
738 ZmPrefBackups.prototype.addPrefBackup =
739 function(Backup) {
740     this._vector.add(Backup);
741     var hash = [Backup.timestamp, "_", Backup.acct].join("");
742     this._zNameHash[hash] = Backup;
743 };
744 
745 ZmPrefBackups.prototype.removePrefBackup =
746 function(Backup) {
747     var hash = [Backup.timestamp, "_", Backup.acct].join("");
748     delete this._zNameHash[hash];
749     this._vector.remove(Backup);
750 };
751 
752 ZmPrefBackups.prototype.getPrefBackupByName =
753 function(hash) {
754     return this._zNameHash[hash];
755 };
756 
757 /**
758  * ZmPrefBackup
759  *
760  * @param timestamp
761  * @param active
762  * @param fileSize
763  * @param acct
764  *
765  * @private
766  */
767 ZmPrefBackup = function(timestamp, active, fileSize, acct) {
768     this.fileSize = fileSize;
769     this.active = (active !== false);
770     this.timestamp = timestamp;
771     this.acct = acct;
772     this._origStatus = this.active;
773 };