/**
* @file js/controllers/HelpPanelHandler.js
*
* Copyright (c) 2014-2021 Simon Fraser University
* Copyright (c) 2000-2021 John Willinsky
* Distributed under the GNU GPL v3. For full terms see the file docs/COPYING.
*
* @class HelpPanelHandler
* @ingroup js_controllers
*
* @brief A handler for the fly-out contextual help panel.
*
* Listens: pkp.HelpPanel.Open
* Listens: pkp.HelpPanel.Close
* Emits: pkp.HelpPanel.Open
* Emits: pkp.HelpPanel.Close
*
* This handler expects to be be attached to an element which matches the
* following base markup. There should only be one help panel on any page.
*
* <div id="pkpHelpPanel" tabindex="-1">
* <div>
* <!-- This handler should only ever interact with the .content div. -->
* <div class="content"></div>
* <button class="pkpCloseHelpPanel"></button>
* </div>
* </div>
*/
(function($) {
/**
* @constructor
*
* @extends $.pkp.classes.Handler
*
* @param {jQueryObject} $element The outer <div> element
* @param {Object} options Handler options.
*/
$.pkp.controllers.HelpPanelHandler = function($element, options) {
this.parent($element, {});
// Let help link events bubble up to the body tag. This way we only
// register one listener once per page load. We won't need to worry
// about content getting swapped out on the page either, as any new
// help links loaded will bubble up to the body tag.
$('body').click(function(e) {
var $self = $(e.target),
options;
if (!$self.hasClass('requestHelpPanel') &&
!$self.parents('.requestHelpPanel').length) {
return;
}
e.preventDefault();
options = $.extend({}, $self.data(), {caller: $self});
$element.trigger('pkp.HelpPanel.Open', options);
});
// Register click handler on close button
$element.find('.pkpCloseHelpPanel').click(function(e) {
e.preventDefault();
$element.trigger('pkp.HelpPanel.Close');
});
// Register click handler on home button
$element.find('.pkpHomeHelpPanel').click(function(e) {
e.preventDefault();
$element.trigger('pkp.HelpPanel.Home');
});
// Handlers for "next" and "previous" buttons
$element.find('.pkpPreviousHelpPanel')
.click(this.callbackWrapper(function(e) {
this.loadHelpContent_(this.previousTopic_, this.helpLocale_);
}));
$element.find('.pkpNextHelpPanel').click(this.callbackWrapper(function(e) {
this.loadHelpContent_(this.nextTopic_, this.helpLocale_);
}));
// Register listeners
$element.on('pkp.HelpPanel.Open', this.callbackWrapper(this.openPanel_))
.on('pkp.HelpPanel.Close', this.callbackWrapper(this.closePanel_))
.on('pkp.HelpPanel.Home', this.callbackWrapper(this.homePanel_));
this.helpUrl_ = options.helpUrl;
this.helpLocale_ = options.helpLocale;
};
$.pkp.classes.Helper.inherits(
$.pkp.controllers.HelpPanelHandler, $.pkp.classes.Handler);
//
// Private properties
//
/**
* Calling element. Focus will be returned here when help panel is closed
* @private
* @type {jQueryObject}
*/
$.pkp.controllers.HelpPanelHandler.prototype.caller_ = null;
/**
* Help subsystem's URL. Used to fetch help content for presentation.
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.helpUrl_ = null;
/**
* Help subsystem's locale. Default: `en`
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.helpLocale_ = null;
/**
* Current help topic
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.currentTopic_ = null;
/**
* Previous help topic
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.previousTopic_ = null;
/**
* Next help topic
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.nextTopic_ = null;
/**
* Requested help section
* @private
* @type {string?}
*/
$.pkp.controllers.HelpPanelHandler.prototype.requestedSection_ = null;
//
// Private methods
//
/**
* Open the helper panel
* @private
* @param {HTMLElement} context The context in which this function was called
* @param {Event} event The event triggered on this handler
* @param {{
* caller: jQueryObject,
* topic: string,
* section: string
* }} options The options with which to open this handler
*/
$.pkp.controllers.HelpPanelHandler.prototype.openPanel_ =
function(context, event, options) {
var $element = this.getHtmlElement();
// Save the calling element
if (typeof options.caller !== 'undefined') {
this.caller_ = options.caller;
}
// Show the help panel
$element.addClass('is_visible');
$('body').addClass('help_panel_is_visible'); // manage scrollbars
// Listen to close interaction events
$element.on('click.pkp.HelpPanel keyup.pkp.HelpPanel',
this.callbackWrapper(this.handleWrapperEvents));
// Listen to clicks on links
$element.on('click.pkp.HelpPanelContentLink', '.content a',
this.callbackWrapper(this.handleContentLinks_));
// Load the appropriate help content
this.loadHelpContent_(options.topic, this.helpLocale_);
this.requestedSection_ = options.section || '';
// Set focus inside the help panel (delay is required so that element is
// visible when jQuery tries to focus on it)
// @todo This should only happen once content is loaded in
setTimeout(function() {
$element.focus();
}, 300);
};
/**
* Load help content in the panel.
* @param {string?} topic The help context.
* @param {string?} locale The language locale to load the help topic.
* @private
*/
$.pkp.controllers.HelpPanelHandler.prototype.loadHelpContent_ =
function(topic, locale) {
locale = locale || this.helpLocale_;
this.currentTopic_ = topic || '';
var url = this.helpUrl_ + '/index/' + locale + '/';
this.getHtmlElement().addClass('is_loading');
// Don't escape slashes
url += encodeURIComponent(this.currentTopic_).replace(/%2F/g, '/');
$.get(url, null, this.callbackWrapper(this.updateContentHandler_),
'json');
};
/**
* A callback to update the tabs on the interface.
* @private
* @param {Object} ajaxContext The AJAX request context.
* @param {Object} jsonData A parsed JSON response object.
*/
$.pkp.controllers.HelpPanelHandler.prototype.
updateContentHandler_ = function(ajaxContext, jsonData) {
var workingJsonData = this.handleJson(jsonData),
responseObject = workingJsonData.content,
helpPanelHandler = this,
$element = this.getHtmlElement(),
hashIndex = this.currentTopic_.indexOf('#'),
$targetHash,
panel = $element.find('.panel');
this.previousTopic_ = responseObject.previous;
this.nextTopic_ = responseObject.next;
// Place the new content into the DOM
$element.find('.content').replaceWith(
'<div class="content">' + responseObject.content + '</div>');
// If a section was specified, scroll to the named section.
panel.scrollTop(0);
if (this.requestedSection_) {
$targetHash = $element.find(
'a[name="' + this.requestedSection_ + '"]');
if ($targetHash.length) {
panel.scrollTop($targetHash.offset().top - 50);
}
}
this.getHtmlElement().removeClass('is_loading');
};
/**
* A callback to handle clicks on links in the help content
*
* This function will allow external links to open in a new window but take
* control of relative links and try to open the appropriate help topic.
*
* @private
* @param {HTMLElement} target The target element the event was triggered on
* @param {Event} event The event triggered on this handler
* @return {boolean} Event handling status.
*/
$.pkp.controllers.HelpPanelHandler.prototype.
handleContentLinks_ = function(target, event) {
var url = $(target).attr('href'),
urlParts,
topic,
locale,
topicParts;
event.preventDefault();
// External links aren't yet supported in the help docs
// See: https://github.com/pkp/pkp-lib/issues/1032#issuecomment-199342940
if (url.substring(0, 4) == 'http') {
window.open(url);
return false;
}
urlParts = url.split('/');
topic = urlParts.slice(1).join('/');
locale = urlParts[0];
if (topic.indexOf('#') > -1) {
topicParts = topic.split('#');
topic = topicParts[0];
this.requestedSection_ = topicParts[1];
}
this.loadHelpContent_(topic, locale);
return false;
};
/**
* Close the helper panel
* @private
*/
$.pkp.controllers.HelpPanelHandler.prototype.closePanel_ = function() {
// Get a reference to this handler's element as a jQuery object
var $element = this.getHtmlElement();
// Show the help panel
$element.removeClass('is_visible');
$('body').removeClass('help_panel_is_visible'); // manage scrollbars
// Clear the help content
$element.find('.content').empty();
// Set focus back to the calling element
if (this.caller_ !== null) {
this.caller_.focus();
}
// Unbind wrapper events from element and reset vars
$element.off('click.pkp.HelpPanel keyup.pkp.HelpPanel');
$element.off('click.pkp.HelpPanelContentLink', '.content a');
this.caller_ = null;
};
/**
* Home the helper panel
* @private
*/
$.pkp.controllers.HelpPanelHandler.prototype.homePanel_ = function() {
this.loadHelpContent_(null, this.helpLocale_);
};
/**
* Process events that reach the wrapper element.
*
* @param {HTMLElement} context The context in which this function was called
* @param {Event} event The event triggered on this handler
*/
$.pkp.controllers.HelpPanelHandler.prototype.handleWrapperEvents =
function(context, event) {
// Get a reference to this handler's element as a jQuery object
var $element = this.getHtmlElement();
// Close click events directly on modal (background screen)
if (event.type == 'click' && $element.is($(event.target))) {
$element.trigger('pkp.HelpPanel.Close');
return;
}
// Close for ESC keypresses (27)
if (event.type == 'keyup' && event.which == 27) {
$element.trigger('pkp.HelpPanel.Close');
return;
}
};
}(jQuery));
|