1/** 2 * @defgroup js_controllers 3 */ 4/** 5 * @file js/controllers/SiteHandler.js 6 * 7 * Copyright (c) 2013-2019 Simon Fraser University 8 * Copyright (c) 2000-2019 John Willinsky 9 * Distributed under the GNU GPL v2. For full terms see the file docs/COPYING. 10 * 11 * @class SiteHandler 12 * @ingroup js_controllers 13 * 14 * @brief Handle the site widget. 15 */ 16(function($) { 17 18 19 /** 20 * @constructor 21 * 22 * @extends $.pkp.classes.Handler 23 * 24 * @param {jQueryObject} $widgetWrapper An HTML element that this handle will 25 * be attached to. 26 * @param {{ 27 * toggleHelpUrl: string, 28 * toggleHelpOffText: string, 29 * toggleHelpOnText: string, 30 * fetchNotificationUrl: string, 31 * requestOptions: Object 32 * }} options Handler options. 33 */ 34 $.pkp.controllers.SiteHandler = function($widgetWrapper, options) { 35 this.parent($widgetWrapper, options); 36 37 this.options_ = options; 38 this.unsavedFormElements_ = []; 39 40 $('.go').button(); 41 42 this.bind('redirectRequested', this.redirectToUrl); 43 this.bind('notifyUser', this.fetchNotificationHandler_); 44 this.bind('updateHeader', this.updateHeaderHandler_); 45 this.bind('updateSidebar', this.updateSidebarHandler_); 46 this.bind('callWhenClickOutside', this.callWhenClickOutsideHandler_); 47 48 // Listen for grid initialized events so the inline help 49 // can be shown or hidden. 50 this.bind('gridInitialized', this.updateHelpDisplayHandler_); 51 52 // Listen for help toggle events. 53 this.bind('toggleInlineHelp', this.toggleInlineHelpHandler_); 54 55 // Bind the pageUnloadHandler_ method to the DOM so it is 56 // called. 57 $(window).bind('beforeunload', this.pageUnloadHandler_); 58 59 // Avoid IE8 caching ajax results. If it does, widgets like 60 // grids will not refresh correctly. 61 $.ajaxSetup({cache: false}); 62 63 this.setMainMaxWidth_(); 64 65 // Check if we have notifications to show. 66 if (options.hasSystemNotifications) { 67 this.trigger('notifyUser'); 68 } 69 70 // bind event handlers for form status change events. 71 this.bind('formChanged', this.callbackWrapper( 72 this.registerUnsavedFormElement_)); 73 this.bind('unregisterChangedForm', this.callbackWrapper( 74 this.unregisterUnsavedFormElement_)); 75 this.bind('modalCanceled', this.callbackWrapper( 76 this.unregisterUnsavedFormElement_)); 77 this.bind('unregisterAllForms', this.callbackWrapper( 78 this.unregisterAllFormElements_)); 79 }; 80 $.pkp.classes.Helper.inherits( 81 $.pkp.controllers.SiteHandler, $.pkp.classes.Handler); 82 83 84 // 85 // Private properties 86 // 87 /** 88 * Site handler options. 89 * @private 90 * @type {Object} 91 */ 92 $.pkp.controllers.SiteHandler.prototype.options_ = null; 93 94 95 /** 96 * A state variable to store the form elements that have unsaved data. 97 * @private 98 * @type {Array} 99 */ 100 $.pkp.controllers.SiteHandler.prototype.unsavedFormElements_ = null; 101 102 103 // 104 // Public methods 105 // 106 /** 107 * Callback that is triggered when the page should redirect. 108 * 109 * @param {HTMLElement} sourceElement The element that issued the 110 * "redirectRequested" event. 111 * @param {Event} event The "redirect requested" event. 112 * @param {string} url The URL to redirect to. 113 */ 114 /*jslint unparam: true*/ 115 $.pkp.controllers.SiteHandler.prototype.redirectToUrl = 116 function(sourceElement, event, url) { 117 window.location = url; 118 }; 119 /*jslint unparam: false*/ 120 121 122 /** 123 * Handler bound to 'formChanged' events propagated by forms 124 * that wish to have their form data tracked. 125 * 126 * @param {HTMLElement} siteHandlerElement The html element 127 * attached to this handler. 128 * @param {HTMLElement} sourceElement The element wishes to 129 * register. 130 * @param {Event} event The formChanged event. 131 * @private 132 */ 133 /*jslint unparam: true*/ 134 $.pkp.controllers.SiteHandler.prototype.registerUnsavedFormElement_ = 135 function(siteHandlerElement, sourceElement, event) { 136 var $formElement, formId, index; 137 138 $formElement = $(event.target.lastElementChild); 139 formId = $formElement.attr('id'); 140 index = $.inArray(formId, this.unsavedFormElements_); 141 if (index == -1) { 142 this.unsavedFormElements_.push(formId); 143 } 144 }; 145 /*jslint unparam: false*/ 146 147 148 /** 149 * Handler bound to 'unregisterChangedForm' events propagated by forms 150 * that wish to inform that they no longer wish to be tracked as 'unsaved'. 151 * 152 * @param {HTMLElement} siteHandlerElement The html element 153 * attached to this handler. 154 * @param {HTMLElement} sourceElement The element that wishes to 155 * unregister. 156 * @param {Event} event The unregisterChangedForm event. 157 * @private 158 */ 159 /*jslint unparam: true*/ 160 $.pkp.controllers.SiteHandler.prototype.unregisterUnsavedFormElement_ = 161 function(siteHandlerElement, sourceElement, event) { 162 var $formElement, formId, index; 163 164 $formElement = $(event.target.lastElementChild); 165 formId = $formElement.attr('id'); 166 index = $.inArray(formId, this.unsavedFormElements_); 167 if (index !== -1) { 168 delete this.unsavedFormElements_[index]; 169 } 170 }; 171 /*jslint unparam: false*/ 172 173 174 /** 175 * Unregister all unsaved form elements. 176 * @private 177 */ 178 $.pkp.controllers.SiteHandler.prototype.unregisterAllFormElements_ = 179 function() { 180 this.unsavedFormElements_ = []; 181 }; 182 183 184 // 185 // Private methods. 186 // 187 /** 188 * Respond to a user toggling the display of inline help. 189 * 190 * @return {boolean} Always returns false. 191 * @private 192 */ 193 $.pkp.controllers.SiteHandler.prototype.toggleInlineHelpHandler_ = 194 function() { 195 // persist the change on the server. 196 $.ajax({url: this.options_.toggleHelpUrl}); 197 198 this.options_.inlineHelpState = this.options_.inlineHelpState ? 0 : 1; 199 this.updateHelpDisplayHandler_(); 200 201 // Stop further event processing 202 return false;
203 }; 204 205 206 /** 207 * Callback to listen to grid initialization events. Used to 208 * toggle the inline help display on them. 209 * 210 * @private 211 */ 212 $.pkp.controllers.SiteHandler.prototype.updateHelpDisplayHandler_ = 213 function() { 214 var $bodyElement, inlineHelpState; 215 216 $bodyElement = this.getHtmlElement(); 217 inlineHelpState = this.options_.inlineHelpState; 218 if (inlineHelpState) { 219 // the .css() call removes the CSS applied to the legend intially, 220 // so it is not shown while the page is being loaded. 221 $bodyElement.find('.pkp_grid_description, #legend, .pkp_help'). 222 css('visibility', 'visible').show(); 223 $bodyElement.find('[id^="toggleHelp"]').html( 224 this.options_.toggleHelpOffText); 225 } else { 226 $bodyElement.find('.pkp_grid_description, #legend, .pkp_help').hide(); 227 $bodyElement.find('[id^="toggleHelp"]').html( 228 this.options_.toggleHelpOnText); 229 } 230 }; 231 232 233 /** 234 * Fetch the notification data. 235 * @param {HTMLElement} sourceElement The element that issued the 236 * "fetchNotification" event. 237 * @param {Event} event The "fetch notification" event. 238 * @param {Object} jsonData The JSON content representing the 239 * notification. 240 * @private 241 */ 242 /*jslint unparam: true*/ 243 $.pkp.controllers.SiteHandler.prototype.fetchNotificationHandler_ = 244 function(sourceElement, event, jsonData) { 245 246 if (jsonData !== undefined) { 247 // This is an event that came from an inplace notification 248 // widget that was not visible because of the scrolling. 249 this.showNotification_(jsonData); 250 return; 251 } 252 253 // Avoid race conditions with in place notifications. 254 $.ajax({ 255 url: this.options_.fetchNotificationUrl, 256 data: this.options_.requestOptions, 257 success: this.callbackWrapper(this.showNotificationResponseHandler_), 258 dataType: 'json', 259 async: false 260 }); 261 }; 262 /*jslint unparam: false*/ 263 264 265 /** 266 * Fetch the header (e.g. on header configuration change). 267 * @private 268 */ 269 $.pkp.controllers.SiteHandler.prototype.updateHeaderHandler_ = 270 function() { 271 var handler = $.pkp.classes.Handler.getHandler($('#headerContainer')); 272 handler.reload(); 273 }; 274 275 276 /** 277 * Fetch the sidebar (e.g. on sidebar configuration change). 278 * @private 279 */ 280 $.pkp.controllers.SiteHandler.prototype.updateSidebarHandler_ = 281 function() { 282 var handler = $.pkp.classes.Handler.getHandler($('#sidebarContainer')); 283 handler.reload(); 284 }; 285 286 287 /** 288 * Binds a click event to this element so we can track if user 289 * clicked outside the passed element or not. 290 * @private 291 * @param {HTMLElement} sourceElement The element that issued the 292 * callWhenClickOutside event. 293 * @param {Event} event The "call when click outside" event. 294 * @param {{ 295 * container: jQueryObject, 296 * callback: Function, 297 * skipWhenVisibleModals: boolean 298 * }}
298 eventParams The event parameters. 299 * - container: a jQuery element to be used to test if user click 300 * outside of it or not. 301 * - callback: a callback function in case test is true. 302 * - skipWhenVisibleModals: boolean flag to tell whether skip the 303 * callback when modals are visible or not. 304 */ 305 /*jslint unparam: true*/ 306 $.pkp.controllers.SiteHandler.prototype.callWhenClickOutsideHandler_ = 307 function(sourceElement, event, eventParams) { 308 if (this.callWhenClickOutsideEventParams_ !== undefined) { 309 throw new Error('Another widget is already using this structure.'); 310 } 311 312 this.callWhenClickOutsideEventParams_ = eventParams; 313 setTimeout(this.callbackWrapper(function() { 314 this.bind('mousedown', this.checkOutsideClickHandler_); 315 }), 25); 316 }; 317 /*jslint unparam: false*/ 318 319 320 /** 321 * Mouse down event handler, used by the callWhenClickOutside event handler 322 * to test if user clicked outside an element or not. If true, will 323 * callback a function. Can optionally avoid the callback 324 * when a modal widget is loaded. 325 * @private 326 * @param {HTMLElement} sourceElement The element that issued the 327 * click event. 328 * @param {Event} event The "mousedown" event. 329 * @return {?boolean} Event handling status. 330 */ 331 /*jslint unparam: true*/ 332 $.pkp.controllers.SiteHandler.prototype.checkOutsideClickHandler_ = 333 function(sourceElement, event) { 334 var $container, callback; 335 336 if (this.callWhenClickOutsideEventParams_ !== undefined) { 337 // Start checking the paramenters. 338 if (this.callWhenClickOutsideEventParams_.container !== undefined) { 339 // Store the container element. 340 $container = this.callWhenClickOutsideEventParams_.container; 341 } else { 342 // Need a container, return. 343 return false; 344 } 345 346 if (this.callWhenClickOutsideEventParams_.callback !== undefined) { 347 // Store the callback. 348 callback = this.callWhenClickOutsideEventParams_.callback; 349 } else { 350 // Need the callback, return. 351 return false; 352 } 353 354 if (this.callWhenClickOutsideEventParams_.skipWhenVisibleModals !== 355 undefined) { 356 if (this.callWhenClickOutsideEventParams_.skipWhenVisibleModals) { 357 if (this.getHtmlElement().find('div.ui-dialog').length > 0) { 358 // Found a modal, return. 359 return false; 360 } 361 } 362 } 363 364 // Do the click origin checking. 365 if ($container.has(event.target).length === 0) { 366 // Unbind this click handler. 367 this.unbind('mousedown', this.checkOutsideClickHandler_); 368 369 // Clean the original event parameters data. 370 this.callWhenClickOutsideEventParams_ = null; 371 372 if (!$container.is(':hidden')) { 373 // Only considered outside if the container is visible. 374 callback(); 375 } 376 } 377 } 378 379 return false; 380 }; 381 /*jslint unparam: false*/ 382 383 384 /** 385 * Internal callback called upon page unload. If it returns 386 * anything other than void, a message will be displayed to 387 * the user. 388 * 389 * @private 390 * 391 * @return {?string} The warning message string, if needed. 392 */ 393 $.pkp.controllers.SiteHandler.prototype.pageUnloadHandler_ = 394 function() { 395 var handler, unsavedElementCount, element; 396 397 // any registered and then unregistered forms will exist 398 // as properties in the unsavedFormElements_ object. They 399 // will just be undefined. See if there are any that are 400 // not. 401 402 // we need to get the handler this way since this event is bound 403 // to window, not to SiteHandler. 404 handler = $.pkp.classes.Handler.getHandler($('body')); 405 406 unsavedElementCount = 0; 407 for (element in handler.unsavedFormElements_) { 408 if (element) { 409 unsavedElementCount++; 410 } 411 } 412 if (unsavedElementCount > 0) { 413 return $.pkp.locale.form_dataHasChanged; 414 } 415 return null; 416 }; 417 418 419 /** 420 * Method to determine if a form is currently registered as having 421 * unsaved changes. 422 * 423 * @param {string} id the id of the form to check. 424 * @return {boolean} true if the form is unsaved. 425 */ 426 $.pkp.controllers.SiteHandler.prototype.isFormUnsaved = 427 function(id) { 428 429 if (this.unsavedFormElements_ !== null && 430 this.unsavedFormElements_[id] !== undefined) { 431 return true; 432 } 433 return false; 434 }; 435 436 437 /** 438 * Response handler to the notification fetch. 439 * 440 * @param {Object} ajaxContext The data returned from the server. 441 * @param {Object} jsonData A parsed JSON response object. 442 * @private 443 */ 444 /*jslint unparam: true*/ 445 $.pkp.controllers.SiteHandler.prototype.showNotificationResponseHandler_ = 446 function(ajaxContext, jsonData) { 447 this.showNotification_(jsonData); 448 }; 449 /*jslint unparam: false*/ 450 451 452 // 453 // Private helper method. 454 // 455 /** 456 * Show the notification content. 457 * 458 * @param {Object} jsonData The JSON-encoded notification data. 459 * @private 460 */ 461 $.pkp.controllers.SiteHandler.prototype.showNotification_ = 462 function(jsonData) { 463 var workingJsonData, notificationsData, levelId, notificationId; 464 465 workingJsonData = this.handleJson(jsonData); 466 if (workingJsonData !== false) { 467 if (workingJsonData.content.general) { 468 notificationsData = workingJsonData.content.general; 469 for (levelId in notificationsData) { 470 for (notificationId in notificationsData[levelId]) { 471 $.pnotify(notificationsData[levelId][notificationId]); 472 } 473 } 474 } 475 } 476 }; 477 478 479 /** 480 * Set the maximum width for the pkp_structure_main div. 481 * This will prevent content with larger widths (like photos) 482 * messing up with layout. 483 * @private 484 */ 485 $.pkp.controllers.SiteHandler.prototype.setMainMaxWidth_ = 486 function() { 487 var $site, structureContentWidth, leftSideBarWidth, rightSideBarWidth, 488 $mainDiv, mainExtraWidth, mainMaxWidth; 489 490 $site = this.getHtmlElement(); 491 structureContentWidth = $('.pkp_structure_content', $site).width(); 492 493 leftSideBarWidth = $('.pkp_structure_sidebar_left', $site). 494 outerWidth(true); 495 rightSideBarWidth = $('.pkp_structure_sidebar_right', $site). 496 outerWidth(true); 497 498 $mainDiv = $('.pkp_structure_main', $site); 499 500 // Check for padding, margin or border. 501 mainExtraWidth = $mainDiv.outerWidth(true) - $mainDiv.width(); 502 mainMaxWidth = structureContentWidth - ( 503 leftSideBarWidth + rightSideBarWidth + mainExtraWidth); 504 505 $mainDiv.css('max-width', mainMaxWidth); 506 }; 507 508 509/** @param {jQuery} $ jQuery closure. */ 510}(jQuery));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.