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 preferences page for managing calendar prefs 26 * @constructor 27 * @class 28 * This class adds specialized handling for managing calendar ACLs that control whether 29 * events can be added to the user's calendar, and who can see the user's free/busy info. 30 * 31 * @author Conrad Damon 32 * 33 * @param {DwtControl} parent the containing widget 34 * @param {Object} section which page we are 35 * @param {ZmPrefController} controller the prefs controller 36 * 37 * @extends ZmPreferencesPage 38 * 39 * @private 40 */ 41 42 ZmCalendarPrefsPage = function(parent, section, controller) { 43 44 ZmPreferencesPage.apply(this, arguments); 45 46 ZmCalendarPrefsPage.TEXTAREA = {}; 47 ZmCalendarPrefsPage.TEXTAREA[ZmSetting.CAL_FREE_BUSY_ACL] = ZmSetting.CAL_FREE_BUSY_ACL_USERS; 48 ZmCalendarPrefsPage.TEXTAREA[ZmSetting.CAL_INVITE_ACL] = ZmSetting.CAL_INVITE_ACL_USERS; 49 ZmCalendarPrefsPage.SETTINGS = [ZmSetting.CAL_FREE_BUSY_ACL, ZmSetting.CAL_INVITE_ACL]; 50 ZmCalendarPrefsPage.RIGHTS = [ZmSetting.RIGHT_VIEW_FREE_BUSY, ZmSetting.RIGHT_INVITE]; 51 52 this._currentSelection = {}; 53 this._initAutocomplete(); 54 }; 55 56 ZmCalendarPrefsPage.prototype = new ZmPreferencesPage; 57 ZmCalendarPrefsPage.prototype.constructor = ZmCalendarPrefsPage; 58 59 ZmCalendarPrefsPage.prototype.isZmCalendarPrefsPage = true; 60 ZmCalendarPrefsPage.prototype.toString = function() { return "ZmCalendarPrefsPage"; }; 61 62 ZmCalendarPrefsPage.prototype.reset = 63 function(useDefaults) { 64 ZmPreferencesPage.prototype.reset.apply(this, arguments); 65 var settings = ZmCalendarPrefsPage.SETTINGS; 66 for (var i = 0; i < settings.length; i++) { 67 this._checkPermTextarea(settings[i]); 68 } 69 if(this._workHoursControl) { 70 this._workHoursControl.reset(); 71 } 72 }; 73 74 ZmCalendarPrefsPage.prototype.showMe = 75 function() { 76 this._acl = appCtxt.getACL(); 77 if (this._acl && !this._acl._loaded) { 78 var respCallback = this._doShowMe.bind(this); 79 this._acl.load(respCallback); 80 } else { 81 this._doShowMe(); 82 } 83 }; 84 85 ZmCalendarPrefsPage.prototype._doShowMe = 86 function() { 87 var settings = ZmCalendarPrefsPage.SETTINGS; 88 var rights = ZmCalendarPrefsPage.RIGHTS; 89 for (var i = 0; i < settings.length; i++) { 90 this._setACLValues(settings[i], rights[i]); 91 } 92 93 var active = appCtxt.getActiveAccount(); 94 this._isAclSupported = !appCtxt.multiAccounts || appCtxt.isFamilyMbox || (!active.isMain && active.isZimbraAccount); 95 96 ZmPreferencesPage.prototype.showMe.call(this); 97 }; 98 99 ZmCalendarPrefsPage.prototype._setupCustom = function (id, setup, value) { 100 switch(id) { 101 case "CAL_WORKING_HOURS": 102 var el = document.getElementById([this._htmlElId, id].join("_")); 103 if(el) { 104 this._workHoursControl = new ZmWorkHours(this, id, value, "WorkHours"); 105 this._workHoursControl.reparentHtmlElement(el); 106 } 107 this.setFormObject(id, this._workHoursControl); 108 break; 109 } 110 111 }; 112 113 ZmCalendarPrefsPage.prototype._getTemplateData = 114 function() { 115 var data = ZmPreferencesPage.prototype._getTemplateData.call(this); 116 data.domain = appCtxt.getUserDomain(); 117 data.isAclSupported = this._isAclSupported; 118 119 return data; 120 }; 121 122 ZmCalendarPrefsPage.prototype._createControls = 123 function() { 124 ZmPreferencesPage.prototype._createControls.apply(this, arguments); 125 126 var settings = ZmCalendarPrefsPage.SETTINGS; 127 128 for (var i = 0; i < settings.length; i++) { 129 var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[settings[i]]); 130 if (textarea && this._acList) { 131 this._acList.handle(textarea.getInputElement()); 132 this._checkPermTextarea(settings[i]); 133 } 134 } 135 }; 136 137 // Sets values for calendar ACL-related settings. 138 ZmCalendarPrefsPage.prototype._setACLValues = 139 function(setting, right) { 140 var gt = this._acl.getGranteeType(right); 141 this._currentSelection[setting] = gt; 142 143 appCtxt.set(setting, gt); 144 var list = this._acl.getGrantees(right); 145 var textDisplay = list.join("\n"); 146 appCtxt.set(ZmCalendarPrefsPage.TEXTAREA[setting], textDisplay); 147 148 this._acl.getGranteeType(right); 149 // Set up the preference initial value (derived from ACL data) so that the 150 // pref is not incorrectly detected as dirty in the _checkSection call. 151 var pref = appCtxt.getSettings().getSetting(setting); 152 pref.origValue = this._currentSelection[setting]; 153 pref = appCtxt.getSettings().getSetting(ZmCalendarPrefsPage.TEXTAREA[setting]); 154 pref.origValue = textDisplay; 155 }; 156 157 /** 158 * ZmPrefView.getChangedPrefs() doesn't quite work for performing a dirty check on this page since 159 * it only returns true if a changed setting is stored in LDAP (has a 'name' property in its ZmSetting 160 * object). This override checks the ACL-related settings to see if they changed. 161 */ 162 ZmCalendarPrefsPage.prototype.isDirty = 163 function(section, list, errors) { 164 var dirty = this._controller.getPrefsView()._checkSection(section, this, true, true, list, errors); 165 if(!dirty && this._workHoursControl) { 166 dirty = this._workHoursControl.isDirty(); 167 } 168 if (!dirty && this._isAclSupported) { 169 this._findACLChanges(); 170 dirty = (this._grants.length || this._revokes.length); 171 } 172 return dirty; 173 }; 174 175 ZmCalendarPrefsPage.prototype._checkPermTextarea = 176 function(setting) { 177 var radioGroup = this.getFormObject(setting); 178 var val = radioGroup && radioGroup.getValue(); 179 var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[setting]); 180 if (textarea && val) { 181 textarea.setEnabled(val == ZmSetting.ACL_USER); 182 } 183 }; 184 185 ZmCalendarPrefsPage.prototype._setupRadioGroup = 186 function(id, setup, value) { 187 var control = ZmPreferencesPage.prototype._setupRadioGroup.apply(this, arguments); 188 var radioGroup = this.getFormObject(id); 189 if (id == ZmSetting.CAL_FREE_BUSY_ACL || id == ZmSetting.CAL_INVITE_ACL) { 190 radioGroup.addSelectionListener(new AjxListener(this, this._checkPermTextarea, [id])); 191 } 192 return control; 193 }; 194 195 ZmCalendarPrefsPage.prototype.getPreSaveCallback = 196 function() { 197 return new AjxCallback(this, this._preSave); 198 }; 199 200 ZmCalendarPrefsPage.prototype.getPostSaveCallback = 201 function() { 202 return new AjxCallback(this, this._postSave); 203 }; 204 205 ZmCalendarPrefsPage.prototype._postSave = 206 function(callback) { 207 var settings = appCtxt.getSettings(); 208 var workHoursSetting = settings.getSetting(ZmSetting.CAL_WORKING_HOURS); 209 workHoursSetting._notify(ZmEvent.E_MODIFY); 210 if (this._workHoursControl) { 211 this._workHoursControl.reloadWorkHours(this._workHoursControl.getValue()); 212 } 213 214 /** 215 * Post save, restore the value of pref zimbraPrefCalendarApptReminderWarningTimevalue 216 * for 'never' and 'at time of event'. In function ZmCalendarApp.setDefaultReminderTimePrefValueOnSave, 217 * if the user has chosen 'never' or 'at time of event' option in default reminder select option, 218 * then on save we make value of never to 0 and 'at time of event' to -1, we are undoing that change here. 219 **/ 220 221 var defaultWarningTime = settings.getSetting(ZmSetting.CAL_REMINDER_WARNING_TIME).getValue(); 222 if (defaultWarningTime === -1 || defaultWarningTime === 0) { // never or 'at time of event' was chosen in defaultreminderpref dropdown 223 defaultWarningTime === -1 ? (defaultWarningTime = 0) : (defaultWarningTime = -1); 224 settings.getSetting(ZmSetting.CAL_REMINDER_WARNING_TIME).setValue(defaultWarningTime); 225 appCtxt.getSettings().getSetting('CAL_REMINDER_WARNING_TIME').origValue = defaultWarningTime; 226 } 227 if (callback instanceof AjxCallback) { 228 callback.run(); 229 } 230 }; 231 232 ZmCalendarPrefsPage.prototype._preSave = 233 function(callback) { 234 if (this._isAclSupported) { 235 this._findACLChanges(); 236 } 237 if (callback) { 238 callback.run(); 239 } 240 }; 241 242 ZmCalendarPrefsPage.prototype._findACLChanges = 243 function() { 244 var settings = ZmCalendarPrefsPage.SETTINGS; 245 var rights = ZmCalendarPrefsPage.RIGHTS; 246 this._grants = []; 247 this._revokes = []; 248 for (var i = 0; i < settings.length; i++) { 249 var result = this._getACLChanges(settings[i], rights[i]); 250 this._grants = this._grants.concat(result.grants); 251 this._revokes = this._revokes.concat(result.revokes); 252 } 253 }; 254 255 ZmCalendarPrefsPage.prototype._getACLChanges = 256 function(setting, right) { 257 258 var curType = appCtxt.get(setting); 259 var curUsers = (curType == ZmSetting.ACL_USER) ? this._acl.getGrantees(right) : []; 260 var curUsersInfo = (curType == ZmSetting.ACL_USER) ? this._acl.getGranteesInfo(right) : []; 261 var zidHash = {}; 262 for (var i = 0; i < curUsersInfo.length; i++) { 263 zidHash[curUsersInfo[i].grantee] = curUsersInfo[i].zid; 264 } 265 var curHash = AjxUtil.arrayAsHash(curUsers); 266 267 var radioGroup = this.getFormObject(setting); 268 var newType = radioGroup.getValue(); 269 var radioGroupChanged = (newType != this._currentSelection[setting]); 270 271 var newUsers = []; 272 if (newType == ZmSetting.ACL_USER) { 273 var textarea = this.getFormObject(ZmCalendarPrefsPage.TEXTAREA[setting]); 274 var val = textarea.getValue(); 275 var users = val.split(/[\n,;]/); 276 for (var i = 0; i < users.length; i++) { 277 var user = AjxStringUtil.trim(users[i]); 278 if (!user) { continue; } 279 if (zidHash[user] != user) { 280 user = (user.indexOf('@') == -1) ? [user, appCtxt.getUserDomain()].join('@') : user; 281 } 282 newUsers.push(user); 283 } 284 newUsers.sort(); 285 } 286 287 var newHash = AjxUtil.arrayAsHash(newUsers); 288 289 var contacts = AjxDispatcher.run("GetContacts"); 290 var grants = []; 291 var revokes = []; 292 if (newUsers.length > 0) { 293 for (var i = 0; i < newUsers.length; i++) { 294 var user = newUsers[i]; 295 if (!curHash[user]) { 296 var contact = contacts.getContactByEmail(user); 297 var gt = (contact && contact.isGroup()) ? ZmSetting.ACL_GROUP : ZmSetting.ACL_USER; 298 var ace = new ZmAccessControlEntry({grantee:user, granteeType:gt, right:right}); 299 grants.push(ace); 300 } 301 } 302 } 303 if (curUsers.length > 0) { 304 for (var i = 0; i < curUsers.length; i++) { 305 var user = curUsers[i]; 306 var zid = (curUsersInfo[i]) ? curUsersInfo[i].zid : null; 307 if (!newHash[user]) { 308 var contact = contacts.getContactByEmail(user); 309 var gt = (contact && contact.isGroup()) ? ZmSetting.ACL_GROUP : ZmSetting.ACL_USER; 310 var ace = new ZmAccessControlEntry({grantee: (user!=zid) ? user : null, granteeType:gt, right:right, zid: zid}); 311 revokes.push(ace); 312 } 313 } 314 } 315 316 var userAdded = (grants.length > 0); 317 var userRemoved = (revokes.length > 0); 318 319 var denyAll = (radioGroupChanged && (newType == ZmSetting.ACL_NONE)); 320 321 if ((newType == ZmSetting.ACL_USER) && (userAdded || userRemoved || radioGroupChanged)) { 322 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN)); 323 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH)); 324 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC)); 325 326 if (newUsers.length == 0) { 327 denyAll = true; 328 } 329 } 330 331 // deny all 332 if (denyAll) { 333 revokes = []; 334 grants = []; 335 336 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN)); 337 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER)); 338 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP)); 339 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC)); 340 341 //deny all 342 var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_AUTH, right:right, negative: true}); 343 grants.push(ace); 344 } 345 346 //allow all users 347 if (radioGroupChanged && (newType == ZmSetting.ACL_PUBLIC)) { 348 grants = []; 349 revokes = []; 350 351 //grant all 352 var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_PUBLIC, right:right}); 353 grants.push(ace); 354 355 //revoke all other aces 356 revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER); 357 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN)); 358 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP)); 359 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH)); 360 } 361 362 if (radioGroupChanged && (newType == ZmSetting.ACL_AUTH)) { 363 grants = []; 364 revokes = []; 365 366 //grant all 367 var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_AUTH, right:right}); 368 grants.push(ace); 369 370 //revoke all other aces 371 revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER); 372 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_DOMAIN)); 373 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP)); 374 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC)); 375 } 376 377 if (radioGroupChanged && (newType == ZmSetting.ACL_DOMAIN)) { 378 grants = []; 379 revokes = []; 380 381 //grant all 382 var ace = new ZmAccessControlEntry({granteeType: ZmSetting.ACL_DOMAIN, right:right, grantee:appCtxt.getUserDomain()}); 383 grants.push(ace); 384 385 //revoke all other aces 386 revokes = this._acl.getACLByGranteeType(right, ZmSetting.ACL_USER); 387 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_GROUP)); 388 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_AUTH)); 389 revokes = revokes.concat(this._acl.getACLByGranteeType(right, ZmSetting.ACL_PUBLIC)); 390 } 391 392 return {grants:grants, revokes:revokes}; 393 }; 394 395 ZmCalendarPrefsPage.prototype.addCommand = 396 function(batchCmd) { 397 if (this._isAclSupported) { 398 var respCallback = new AjxCallback(this, this._handleResponseACLChange); 399 if (this._revokes.length) { 400 this._acl.revoke(this._revokes, respCallback, batchCmd); 401 } 402 if (this._grants.length) { 403 this._acl.grant(this._grants, respCallback, batchCmd); 404 } 405 } 406 407 if(this._workHoursControl) { 408 if(this._workHoursControl.isValid()) { 409 var value = this._workHoursControl.getValue(), 410 soapDoc = AjxSoapDoc.create("ModifyPrefsRequest", "urn:zimbraAccount"), 411 node = soapDoc.set("pref", value), 412 respCallback = new AjxCallback(this, this._postSaveBatchCmd, [value]); 413 node.setAttribute("name", "zimbraPrefCalendarWorkingHours"); 414 batchCmd.addNewRequestParams(soapDoc, respCallback); 415 } 416 else { 417 throw new AjxException(ZmMsg.calendarWorkHoursInvalid); 418 } 419 } 420 }; 421 422 ZmCalendarPrefsPage.prototype._postSaveBatchCmd = 423 function(value) { 424 appCtxt.set(ZmSetting.CAL_WORKING_HOURS, value); 425 var firstDayOfWeek = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0; 426 427 if(this._workHoursControl) { 428 // Check if either work days have changed or first day of week has changed. 429 // Need to reload the browser either way to show the correct changes. 430 if(this._workHoursControl.getDaysChanged() || 431 parseInt(this._workHoursControl._workDaysCheckBox[0].getValue()) != firstDayOfWeek) { 432 this._workHoursControl.setDaysChanged(false); 433 var cd = appCtxt.getYesNoMsgDialog(); 434 cd.reset(); 435 cd.registerCallback(DwtDialog.YES_BUTTON, this._newWorkHoursYesCallback, this, [skin, cd]); 436 cd.setMessage(ZmMsg.workingDaysRestart, DwtMessageDialog.WARNING_STYLE); 437 cd.popup(); 438 } 439 } 440 }; 441 442 ZmCalendarPrefsPage.prototype._newWorkHoursYesCallback = 443 function(skin, dialog) { 444 dialog.popdown(); 445 window.onbeforeunload = null; 446 var url = AjxUtil.formatUrl(); 447 DBG.println(AjxDebug.DBG1, "Working days change, redirect to: " + url); 448 ZmZimbraMail.sendRedirect(url); // redirect to self to force reload 449 }; 450 451 ZmCalendarPrefsPage.prototype._handleResponseACLChange = 452 function(aces) { 453 if (aces && !(aces instanceof Array)) { aces = [aces]; } 454 455 if (aces && aces.length) { 456 for (var i = 0; i < aces.length; i++) { 457 var ace = aces[i]; 458 var setting = (ace.right == ZmSetting.RIGHT_INVITE) ? ZmSetting.CAL_INVITE_ACL : ZmSetting.CAL_FREE_BUSY_ACL; 459 this._setACLValues(setting, ace.right); 460 } 461 } 462 }; 463 464 ZmCalendarPrefsPage.prototype._initAutocomplete = 465 function() { 466 if (appCtxt.get(ZmSetting.CONTACTS_ENABLED) && appCtxt.get(ZmSetting.GAL_AUTOCOMPLETE_ENABLED)) { 467 var contactsClass = appCtxt.getApp(ZmApp.CONTACTS); 468 var contactsLoader = contactsClass.getContactList; 469 var params = { 470 dataClass: appCtxt.getAutocompleter(), 471 separator: ";", 472 matchValue: ZmAutocomplete.AC_VALUE_EMAIL, 473 options: {galOnly:true}, 474 contextId: this.toString() 475 }; 476 this._acList = new ZmAutocompleteListView(params); 477 } 478 }; 479 480 /** 481 * Creates the WorkHours custom control 482 * 483 * @constructor 484 * @param parent 485 * @param id 486 * @param value work hours string 487 * @param templateId 488 */ 489 ZmWorkHours = function(parent, id, value, templateId) { 490 DwtComposite.call(this, {parent:parent, id: Dwt.getNextId(id)}); 491 492 493 this._workDaysCheckBox = []; 494 this._startTimeSelect = null; 495 this._endTimeSelect = null; 496 this._customDlg = null; 497 this._customBtn = null; 498 this.reloadWorkHours(value); 499 this._radioNormal = null; 500 this._radioCustom = null; 501 this._daysChanged = false; 502 this._setContent(templateId); 503 }; 504 505 ZmWorkHours.STR_DAY_SEP = ","; 506 ZmWorkHours.STR_TIME_SEP = ":"; 507 508 ZmWorkHours.prototype = new DwtComposite; 509 ZmWorkHours.prototype.constructor = ZmWorkHours; 510 511 ZmWorkHours.prototype.toString = 512 function() { 513 return "ZmWorkHours"; 514 }; 515 516 ZmWorkHours.prototype.getTabGroup = ZmWorkHours.prototype.getTabGroupMember; 517 518 ZmWorkHours.prototype.reloadWorkHours = 519 function(value) { 520 value = value || appCtxt.get(ZmSetting.CAL_WORKING_HOURS); 521 var workHours = this._workHours = this.decodeWorkingHours(value), 522 dayIdx = new Date().getDay(); 523 this._startTime = new Date(); 524 this._endTime = new Date(); 525 this._startTime.setHours(workHours[dayIdx].startTime/100, workHours[dayIdx].startTime%100, 0); 526 this._endTime.setHours(workHours[dayIdx].endTime/100, workHours[dayIdx].endTime%100, 0); 527 this._isCustom = this._isCustomTimeSet(); 528 if(this._customDlg) { 529 this._customDlg.reloadWorkHours(workHours); 530 } 531 }; 532 533 ZmWorkHours.prototype.reset = 534 function() { 535 if (!this.isDirty()) { return; } 536 var i, 537 workHours = this._workHours; 538 539 this._startTimeSelect.set(this._startTime); 540 this._endTimeSelect.set(this._endTime); 541 542 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 543 this._workDaysCheckBox[i].setSelected(workHours[i].isWorkingDay); 544 } 545 546 this._radioGroup.setSelectedId(this._isCustom ? this._radioCustomId : this._radioNormalId); 547 548 // Reset the custom work hours dialog as well 549 if (this._customDlg) { 550 this._customDlg.reset(); 551 } 552 }; 553 554 ZmWorkHours.prototype.isDirty = 555 function() { 556 var i, 557 isDirty = false, 558 workHours = this._workHours, 559 tf = new AjxDateFormat("HHmm"), 560 startInputTime = tf.format(this._startTimeSelect.getValue()), 561 endInputTime = tf.format(this._endTimeSelect.getValue()), 562 isCustom = this._radioCustom.isSelected(); 563 564 565 if(!isCustom) { 566 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 567 if(this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) { 568 this.setDaysChanged(true); 569 isDirty = true; 570 break; 571 } 572 } 573 574 if(startInputTime != workHours[0].startTime || endInputTime != workHours[0].endTime) { 575 isDirty = true; 576 } 577 } 578 else if(this._customDlg) { 579 isDirty = this._customDlg.isDirty(); 580 } 581 582 if (!isCustom && this._isCustom) { //switching to normal should trigger dirty anyway. 583 isDirty = true; 584 } 585 586 return isDirty; 587 }; 588 589 ZmWorkHours.prototype.setDaysChanged = 590 function(value) { 591 var isCustom = this._radioCustom.isSelected(); 592 if(isCustom && this._customDlg) { 593 this._customDlg.setDaysChanged(value); 594 } 595 else { 596 this._daysChanged = value; 597 } 598 }; 599 600 ZmWorkHours.prototype.getDaysChanged = 601 function() { 602 var isCustom = this._radioCustom.isSelected(); 603 if(isCustom && this._customDlg) { 604 return this._customDlg.getDaysChanged(); 605 } 606 else { 607 return this._daysChanged; 608 } 609 }; 610 611 ZmWorkHours.prototype.isValid = 612 function() { 613 var tf = new AjxDateFormat("HHmm"), 614 startInputTime = tf.format(this._startTimeSelect.getValue()), 615 endInputTime = tf.format(this._endTimeSelect.getValue()); 616 if(this._radioCustom.isSelected() && this._customDlg) { 617 return this._customDlg.isValid(); 618 } 619 return startInputTime < endInputTime ? true : false; 620 }; 621 622 ZmWorkHours.prototype.decodeWorkingHours = 623 function(wHrsString) { 624 if(wHrsString === 0) { 625 return []; 626 } 627 var wHrsPerDay = wHrsString.split(ZmWorkHours.STR_DAY_SEP), 628 i, 629 wHrs = [], 630 wDay, 631 w, 632 idx; 633 634 for(i=0; i<wHrsPerDay.length; i++) { 635 wDay = wHrsPerDay[i].split(ZmWorkHours.STR_TIME_SEP); 636 w = {}; 637 w.dayOfWeek = wDay[0] - 1; 638 w.isWorkingDay = (wDay[1] === "Y"); 639 w.startTime = wDay[2]; 640 w.endTime = wDay[3]; 641 642 wHrs[i] = w; 643 } 644 return wHrs; 645 }; 646 647 ZmWorkHours.prototype.encodeWorkingHours = 648 function() { 649 var i, 650 tf = new AjxDateFormat("HHmm"), 651 startInputTime = tf.format(this._startTimeSelect.getValue()), 652 endInputTime = tf.format(this._endTimeSelect.getValue()), 653 dayStr, 654 wDaysStr = []; 655 656 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 657 dayStr = []; 658 dayStr.push(parseInt(this._workDaysCheckBox[i].getValue()) + 1); 659 if(this._workDaysCheckBox[i].isSelected()) { 660 dayStr.push("Y"); 661 } 662 else { 663 dayStr.push("N"); 664 } 665 dayStr.push(startInputTime); 666 dayStr.push(endInputTime); 667 wDaysStr.push(dayStr.join(ZmWorkHours.STR_TIME_SEP)); 668 } 669 return wDaysStr.join(ZmWorkHours.STR_DAY_SEP); 670 }; 671 672 ZmWorkHours.prototype._isCustomTimeSet = 673 function() { 674 var i, 675 w = this._workHours; 676 for (i=1;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 677 if(w[i].startTime != w[i-1].startTime || w[i].endTime != w[i-1].endTime) { 678 return true; 679 } 680 } 681 return false; 682 }; 683 684 ZmWorkHours.prototype._closeCustomDialog = 685 function(value) { 686 this._customDlg.popdown(); 687 }; 688 689 ZmWorkHours.prototype._closeCancelCustomDialog = function(value) { 690 if (this._customDlg.isDirty()) { 691 this._customDlg.reset(); 692 } 693 this._customDlg.popdown(); 694 }; 695 696 ZmWorkHours.prototype._openCustomizeDlg = 697 function() { 698 if(!this._customDlg) { 699 this._customDlg = new ZmCustomWorkHoursDlg(appCtxt.getShell(), "CustomWorkHoursDlg", this._workHours); 700 this._customDlg.initialize(this._workHours); 701 this._customDlg.setButtonListener(DwtDialog.OK_BUTTON, new AjxListener(this, this._closeCustomDialog, [true])); 702 this._customDlg.setButtonListener(DwtDialog.CANCEL_BUTTON, new AjxListener(this, this._closeCancelCustomDialog, [false])); 703 } 704 this._customDlg.popup(); 705 }; 706 707 ZmWorkHours.prototype.getValue = 708 function() { 709 if(this._radioCustom.isSelected()) { 710 if(this._customDlg) { 711 return this._customDlg.getValue(); 712 } 713 else { 714 this._radioNormal.setSelected(true); 715 this._radioCustom.setSelected(false); 716 this._toggleNormalCustom(); 717 } 718 } 719 return this.encodeWorkingHours(); 720 }; 721 722 ZmWorkHours.prototype._toggleNormalCustom = 723 function() { 724 var i; 725 if(this._radioNormal.isSelected()) { 726 this._startTimeSelect.setEnabled(true); 727 this._endTimeSelect.setEnabled(true); 728 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 729 this._workDaysCheckBox[i].setEnabled(true); 730 } 731 this._customBtn.setEnabled(false); 732 } 733 else { 734 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 735 this._workDaysCheckBox[i].setEnabled(false); 736 } 737 this._startTimeSelect.setEnabled(false); 738 this._endTimeSelect.setEnabled(false); 739 this._customBtn.setEnabled(true); 740 } 741 }; 742 743 ZmWorkHours.prototype._setContent = 744 function(templateId) { 745 var i, 746 el, 747 checkbox, 748 customBtn, 749 workHours = this._workHours, 750 startTimeSelect, 751 endTimeSelect, 752 radioNormal, 753 radioCustom, 754 radioGroup, 755 selectedRadioId, 756 radioIds = {}, 757 radioName = this._htmlElId + "_normalCustom", 758 isCustom = this._isCustom; 759 760 this.getHtmlElement().innerHTML = AjxTemplate.expand("prefs.Pages#"+templateId, {id:this._htmlElId}); 761 //fill the containers for the work days and work time 762 var firstDayOfWeek = appCtxt.get(ZmSetting.CAL_FIRST_DAY_OF_WEEK) || 0; 763 764 // Figure out where in the workHours array the info for firstDayOfWeek is located. 765 // The first item in array is associated with the first day of the week that has been set. 766 // This determines how the checkboxes will be displayed for work week days. Getting 767 // the correct position in the array for first day of week will allow the correct 768 // workHours[].isworkingDay value to be set. 769 var startingIndex = 0; 770 for (i = 0; i < AjxDateUtil.DAYS_PER_WEEK; i++) { 771 if (workHours[i].dayOfWeek == firstDayOfWeek) { 772 startingIndex = i; 773 break; 774 } 775 } 776 777 for (i = 0; i < AjxDateUtil.DAYS_PER_WEEK; i++) { 778 var dayIndex = (i + startingIndex) % AjxDateUtil.DAYS_PER_WEEK; 779 var dayOfWeek = (i + firstDayOfWeek) % AjxDateUtil.DAYS_PER_WEEK; 780 781 checkbox = new DwtCheckbox({parent:this, parentElement:(this._htmlElId + "_CAL_WORKING_DAY_" + i)}); 782 checkbox.setText(AjxDateUtil.WEEKDAY_MEDIUM[dayOfWeek]); 783 checkbox.setToolTipContent(AjxDateUtil.WEEKDAY_LONG[dayOfWeek]); 784 checkbox.setValue(dayOfWeek); 785 checkbox.setSelected(workHours[dayIndex].isWorkingDay); 786 checkbox.setEnabled(!isCustom) 787 this._workDaysCheckBox.push(checkbox); 788 } 789 790 radioNormal = new DwtRadioButton({parent:this, name:radioName, parentElement:(this._htmlElId + "_CAL_WORKING_HOURS_NORMAL")}); 791 radioNormal.setSelected(!isCustom); 792 var radioNormalId = radioNormal.getInputElement().id; 793 radioIds[radioNormalId] = radioNormal; 794 this._radioNormal = radioNormal; 795 this._radioNormalId = radioNormalId; 796 797 el = document.getElementById(this._htmlElId + "_CAL_WORKING_START_TIME"); 798 startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START, el); 799 startTimeSelect.set(this._startTime); 800 startTimeSelect.setEnabled(!isCustom); 801 this._startTimeSelect = startTimeSelect; 802 803 el = document.getElementById(this._htmlElId + "_CAL_WORKING_END_TIME"); 804 endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END, el); 805 endTimeSelect.set(this._endTime); 806 endTimeSelect.setEnabled(!isCustom); 807 this._endTimeSelect = endTimeSelect; 808 809 radioCustom = new DwtRadioButton({parent:this, name:radioName, parentElement:(this._htmlElId + "_CAL_WORKING_HOURS_CUSTOM")}); 810 radioCustom.setSelected(isCustom); 811 var radioCustomId = radioCustom.getInputElement().id; 812 radioIds[radioCustomId] = radioCustom; 813 this._radioCustom = radioCustom; 814 this._radioCustomId = radioCustomId; 815 816 radioGroup = new DwtRadioButtonGroup(radioIds, isCustom ? radioCustomId : radioNormalId); 817 818 radioGroup.addSelectionListener(new AjxListener(this, this._toggleNormalCustom)); 819 this._radioGroup = radioGroup; 820 821 customBtn = new DwtButton({parent:this, parentElement:(this._htmlElId + "_CAL_CUSTOM_WORK_HOURS")}); 822 customBtn.setText(ZmMsg.calendarCustomBtnTitle); 823 customBtn.addSelectionListener(new AjxListener(this, this._openCustomizeDlg)); 824 customBtn.setEnabled(isCustom); 825 this._customBtn = customBtn; 826 }; 827 828 /** 829 * Create the custom work hours dialog box 830 * @param parent 831 * @param templateId 832 * @param workHours the work hours parsed array 833 */ 834 ZmCustomWorkHoursDlg = function (parent, templateId, workHours) { 835 DwtDialog.call(this, {parent:parent}); 836 this._workHours = workHours; 837 this._startTimeSelect = []; 838 this._endTimeSelect = []; 839 this._workDaysCheckBox = []; 840 var contentHtml = AjxTemplate.expand("prefs.Pages#"+templateId, {id:this._htmlElId}); 841 this.setContent(contentHtml); 842 this.setTitle(ZmMsg.calendarCustomDlgTitle); 843 this._daysChanged = false; 844 }; 845 846 ZmCustomWorkHoursDlg.prototype = new DwtDialog; 847 ZmCustomWorkHoursDlg.prototype.constructor = ZmCustomWorkHoursDlg; 848 849 ZmCustomWorkHoursDlg.prototype.initialize = function(workHours) { 850 var i, 851 el, 852 checkbox, 853 startTimeSelect, 854 endTimeSelect, 855 inputTime; 856 857 workHours = workHours || this._workHours; 858 859 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 860 //fill the containers for the work days and work time 861 el = document.getElementById(this._htmlElId + "_CAL_WORKING_START_TIME_"+i); 862 startTimeSelect = new DwtTimeInput(this, DwtTimeInput.START, el); 863 inputTime = new Date(); 864 inputTime.setHours(workHours[i].startTime/100, workHours[i].startTime%100, 0); 865 startTimeSelect.set(inputTime); 866 startTimeSelect.setEnabled(workHours[i].isWorkingDay); 867 this._startTimeSelect.push(startTimeSelect); 868 869 870 inputTime = new Date(); 871 inputTime.setHours(workHours[i].endTime/100, workHours[i].endTime%100, 0); 872 el = document.getElementById(this._htmlElId + "_CAL_WORKING_END_TIME_"+i); 873 endTimeSelect = new DwtTimeInput(this, DwtTimeInput.END, el); 874 endTimeSelect.set(inputTime); 875 endTimeSelect.setEnabled(workHours[i].isWorkingDay); 876 this._endTimeSelect.push(endTimeSelect); 877 878 879 checkbox = new DwtCheckbox({parent:this, parentElement:(this._htmlElId + "_CAL_WORKING_DAY_" + i)}); 880 checkbox.setText(AjxDateUtil.WEEKDAY_MEDIUM[i]); 881 checkbox.setSelected(workHours[i].isWorkingDay); 882 checkbox.addSelectionListener(new AjxListener(this, this._setTimeInputEnabled, [i, checkbox])); 883 this._workDaysCheckBox.push(checkbox); 884 } 885 }; 886 887 ZmCustomWorkHoursDlg.prototype.reset = 888 function() { 889 var i, 890 inputTime; 891 892 for (i = 0; i < AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 893 inputTime = new Date(); 894 inputTime.setHours(this._workHours[i].startTime/100, this._workHours[i].startTime%100, 0); 895 this._startTimeSelect[i].set(inputTime); 896 this._startTimeSelect[i].setEnabled(this._workHours[i].isWorkingDay); 897 898 inputTime = new Date(); 899 inputTime.setHours(this._workHours[i].endTime/100, this._workHours[i].endTime%100, 0); 900 this._endTimeSelect[i].set(inputTime); 901 this._endTimeSelect[i].setEnabled(this._workHours[i].isWorkingDay); 902 903 this._workDaysCheckBox[i].setSelected(this._workHours[i].isWorkingDay); 904 } 905 } 906 907 ZmCustomWorkHoursDlg.prototype.reloadWorkHours = 908 function(workHours) { 909 workHours = workHours || appCtxt.get(ZmSetting.CAL_WORKING_HOURS); 910 this._workHours = workHours; 911 }; 912 913 ZmCustomWorkHoursDlg.prototype._setTimeInputEnabled = 914 function(idx, checkbox) { 915 this._startTimeSelect[idx].setEnabled(checkbox.isSelected()); 916 this._endTimeSelect[idx].setEnabled(checkbox.isSelected()); 917 }; 918 919 ZmCustomWorkHoursDlg.prototype.isDirty = 920 function() { 921 var i, 922 workHours = this._workHours, 923 tf = new AjxDateFormat("HHmm"), 924 startInputTime, 925 endInputTime; 926 927 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 928 if(this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) { 929 this.setDaysChanged(true); 930 return true; 931 } 932 } 933 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 934 startInputTime = tf.format(this._startTimeSelect[i].getValue()); 935 endInputTime = tf.format(this._endTimeSelect[i].getValue()); 936 937 if(startInputTime != workHours[i].startTime 938 || endInputTime != workHours[i].endTime 939 || this._workDaysCheckBox[i].isSelected() != workHours[i].isWorkingDay) { 940 return true; 941 } 942 } 943 return false; 944 }; 945 946 ZmCustomWorkHoursDlg.prototype.popup = 947 function() { 948 DwtDialog.prototype.popup.call(this); 949 }; 950 951 ZmCustomWorkHoursDlg.prototype.isValid = 952 function() { 953 var i, 954 tf = new AjxDateFormat("HHmm"), 955 startInputTime, 956 endInputTime; 957 958 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 959 if(this._workDaysCheckBox[i].isSelected()) { 960 startInputTime = tf.format(this._startTimeSelect[i].getValue()); 961 endInputTime = tf.format(this._endTimeSelect[i].getValue()); 962 963 if(startInputTime > endInputTime) { 964 return false; 965 } 966 } 967 } 968 return true; 969 970 }; 971 972 ZmCustomWorkHoursDlg.prototype.getValue = 973 function() { 974 var i, 975 tf = new AjxDateFormat("HHmm"), 976 startInputTime, 977 endInputTime, 978 dayStr, 979 wDaysStr = []; 980 981 for (i=0;i<AjxDateUtil.WEEKDAY_MEDIUM.length; i++) { 982 startInputTime = tf.format(this._startTimeSelect[i].getValue()); 983 endInputTime = tf.format(this._endTimeSelect[i].getValue()); 984 dayStr = []; 985 dayStr.push(i+1); 986 if(this._workDaysCheckBox[i].isSelected()) { 987 dayStr.push("Y"); 988 } 989 else { 990 dayStr.push("N"); 991 } 992 dayStr.push(startInputTime); 993 dayStr.push(endInputTime); 994 wDaysStr.push(dayStr.join(ZmWorkHours.STR_TIME_SEP)); 995 } 996 return wDaysStr.join(ZmWorkHours.STR_DAY_SEP); 997 }; 998 999 ZmCustomWorkHoursDlg.prototype.setDaysChanged = 1000 function(value) { 1001 this._daysChanged = value; 1002 }; 1003 1004 ZmCustomWorkHoursDlg.prototype.getDaysChanged = 1005 function() { 1006 return this._daysChanged; 1007 }; 1008 1009 1010