857 lines
24 KiB
JavaScript
857 lines
24 KiB
JavaScript
/**
|
|
* @defgroup js_controllers_listbuilder
|
|
*/
|
|
/**
|
|
* @file js/controllers/listbuilder/ListbuilderHandler.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 ListbuilderHandler
|
|
* @ingroup js_controllers_listbuilder
|
|
*
|
|
* @brief Listbuilder row handler.
|
|
*/
|
|
(function($) {
|
|
|
|
/** @type {Object} */
|
|
$.pkp.controllers.listbuilder = $.pkp.controllers.listbuilder || {};
|
|
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @extends $.pkp.controllers.grid.GridHandler
|
|
*
|
|
* @param {jQueryObject} $listbuilder The listbuilder this handler is
|
|
* attached to.
|
|
* @param {Object} options Listbuilder handler configuration.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler =
|
|
function($listbuilder, options) {
|
|
this.parent($listbuilder, options);
|
|
};
|
|
$.pkp.classes.Helper.inherits($.pkp.controllers.listbuilder.ListbuilderHandler,
|
|
$.pkp.controllers.grid.GridHandler);
|
|
|
|
|
|
//
|
|
// Private properties
|
|
//
|
|
/**
|
|
* The source type (LISTBUILDER_SOURCE_TYPE_...) of the listbuilder.
|
|
* @private
|
|
* @type {?number}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
sourceType_ = null;
|
|
|
|
|
|
/**
|
|
* The "save" URL of the listbuilder (for
|
|
* LISTBUILDER_SAVE_TYPE_INTERNAL).
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
saveUrl_ = null;
|
|
|
|
|
|
/**
|
|
* The "save" field name of the listbuilder (for
|
|
* LISTBUILDER_SAVE_TYPE_EXTERNAL).
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
saveFieldName_ = null;
|
|
|
|
|
|
/**
|
|
* The "fetch options" URL of the listbuilder (for "select" source type).
|
|
* @private
|
|
* @type {?string}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
fetchOptionsUrl_ = null;
|
|
|
|
|
|
/**
|
|
* Stores the calling context of the edit item click event.
|
|
* @private
|
|
* @type {HTMLElement}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.editItemCallingContext_ = null;
|
|
|
|
|
|
/**
|
|
* Flag whether there's still available options to be selected or not.
|
|
* @private
|
|
* @type {boolean}
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.availableOptions_ = false;
|
|
|
|
|
|
//
|
|
// Protected methods
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.initialize =
|
|
function(options) {
|
|
this.parent('initialize', options);
|
|
|
|
// Save listbuilder options
|
|
this.sourceType_ = options.sourceType;
|
|
this.saveUrl_ = options.saveUrl;
|
|
this.saveFieldName_ = options.saveFieldName;
|
|
this.fetchOptionsUrl_ = options.fetchOptionsUrl;
|
|
this.availableOptions_ = options.availableOptions;
|
|
|
|
// Attach the button handlers
|
|
var $listbuilder = this.getHtmlElement();
|
|
// Use mousedown to avoid two events being triggered at the same time
|
|
// (click event was being triggered together with blur event from inputs.
|
|
// That and a syncronous ajax call triggered by those events
|
|
// handlers, was leading to an error in IE8 and it was freezing
|
|
// Firefox 13.0).
|
|
$listbuilder.find('.actions .pkp_linkaction_addItem').mousedown(
|
|
this.callbackWrapper(this.addItemHandler_));
|
|
|
|
// Attach the content manipulation handlers
|
|
this.attachContentHandlers_($listbuilder);
|
|
|
|
// Sign up for notification of form submission.
|
|
this.bind('formSubmitRequested', this.formSubmitHandler_);
|
|
|
|
// Sign up for notification of form submitted.
|
|
this.bind('formSubmitted', this.formSubmittedHandler_);
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the "save" URL for LISTBUILDER_SAVE_TYPE_INTERNAL.
|
|
* @private
|
|
* @return {?string} URL to the "save listbuilder" handler operation.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getSaveUrl_ =
|
|
function() {
|
|
|
|
return this.saveUrl_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the "save" field name for LISTBUILDER_SAVE_TYPE_EXTERNAL.
|
|
* @private
|
|
* @return {string} Name of the field to transmit LB contents in.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getSaveFieldName_ =
|
|
function() {
|
|
|
|
return /** @type {string} */ (this.saveFieldName_);
|
|
};
|
|
|
|
|
|
/**
|
|
* "Save" and close any editing rows in the listbuilder.
|
|
* @protected
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.closeEdits =
|
|
function() {
|
|
|
|
var $editedRow = this.getHtmlElement().find('.gridRowEdit:visible');
|
|
if ($editedRow.length !== 0) {
|
|
this.saveRow($editedRow);
|
|
$editedRow.removeClass('gridRowEdit');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Save the listbuilder.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.save =
|
|
function() {
|
|
|
|
// Get deletions
|
|
var deletions = this.getHtmlElement().find('input.deletions').val(),
|
|
// Get insertions and modifications
|
|
changes = [],
|
|
numberOfRows, stringifiedData, saveUrl,
|
|
saveFieldName, $e,
|
|
handler = this;
|
|
|
|
this.getHtmlElement().find('.gridRow input.isModified[value="1"]')
|
|
.each(function(index, v) {
|
|
var $row = $(v).parents('.gridRow'),
|
|
params = handler.buildParamsFromInputs_($row.find(':input'));
|
|
changes.push(params);
|
|
});
|
|
|
|
// The listbuilder form validator needs to know if this listbuilder contains
|
|
// rows or not, so we pass the items number.
|
|
numberOfRows = this.getRows().length;
|
|
|
|
// Assemble and send to the server
|
|
stringifiedData = JSON.stringify(
|
|
{deletions: deletions, changes: changes, numberOfRows: numberOfRows});
|
|
saveUrl = this.getSaveUrl_();
|
|
if (saveUrl) {
|
|
// Post the changes to the server using the internal
|
|
// save handler.
|
|
$.post(saveUrl, {data: stringifiedData},
|
|
this.callbackWrapper(this.saveResponseHandler_, null), 'json');
|
|
} else {
|
|
// Supply the data to an external save handler (e.g.
|
|
// a form handler) using a hidden field.
|
|
saveFieldName = this.getSaveFieldName_();
|
|
|
|
// Try to find and reuse an existing element (if
|
|
// e.g. a previous attempt was aborted)
|
|
$e = this.getHtmlElement()
|
|
.find(':input[type=hidden]')
|
|
.filter(
|
|
function() {return $(this).attr('name') == saveFieldName;})
|
|
.first();
|
|
|
|
// If we couldn't find one, create one.
|
|
if ($e.length === 0) {
|
|
$e = $('<input type="hidden" />');
|
|
$e.attr('name', saveFieldName);
|
|
this.getHtmlElement().append($e);
|
|
}
|
|
|
|
// Set the value of the hidden element.
|
|
$e.attr('value', stringifiedData);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Function that will be called to save an edited row.
|
|
* @param {Object} $row The DOM element representing the row to save.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
saveRow = function($row) {
|
|
|
|
// Retrieve a single new row from the server.
|
|
// (Avoid IE closure leak using this flag rather than passing
|
|
// around a DOM element in a closure.)
|
|
$row.addClass('saveRowResponsePlaceholder');
|
|
var params = this.buildParamsFromInputs_($row.find(':input'));
|
|
params.modify = true; // Flag the row for modification
|
|
// Use a blocking request to avoid race conditions sometimes
|
|
// duplicating items, i.e. when editing an existing item after
|
|
// adding a new one.
|
|
this.disableControls();
|
|
$.ajax({
|
|
url: this.getFetchRowUrl(),
|
|
data: params,
|
|
success: this.callbackWrapper(this.saveRowResponseHandler_, null),
|
|
dataType: 'json',
|
|
async: false
|
|
});
|
|
};
|
|
|
|
|
|
//
|
|
// Extended methods from GridHandler.
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.getEmptyElement =
|
|
function($element) {
|
|
// Listbuilders have only one empty element placeholder.
|
|
return this.getHtmlElement().find('.empty');
|
|
};
|
|
|
|
|
|
//
|
|
// Private Methods
|
|
//
|
|
/**
|
|
* Callback that will be activated when the "add item" icon is clicked
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} callingContext The calling element or object.
|
|
* @param {Event=} opt_event The triggering event (e.g. a click on
|
|
* a button.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.addItemHandler_ =
|
|
function(callingContext, opt_event) {
|
|
|
|
if (this.availableOptions_) {
|
|
// Make sure this event will be handled after any other next triggered one,
|
|
// like blur event that comes from inputs.
|
|
setTimeout(this.callbackWrapper(function() {
|
|
// Close any existing edits if necessary
|
|
this.closeEdits();
|
|
|
|
this.disableControls();
|
|
$.get(this.getFetchRowUrl(), {modify: true},
|
|
this.callbackWrapper(this.appendRowResponseHandler_, null), 'json');
|
|
}), 0);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated when a delete icon is clicked
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} callingContext The calling element or object.
|
|
* @param {Event=} opt_event The triggering event (e.g. a click on
|
|
* a button.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.deleteItemHandler_ =
|
|
function(callingContext, opt_event) {
|
|
|
|
// Close any existing edits if necessary
|
|
this.closeEdits();
|
|
|
|
var $callingContext = $(callingContext),
|
|
$targetRow = $callingContext.closest('.gridRow'),
|
|
$deletions = $callingContext.closest('.pkp_controllers_listbuilder')
|
|
.find('.deletions'),
|
|
rowId = $targetRow.find('input[name="rowId"]').val();
|
|
|
|
// Append the row ID to the deletions list.
|
|
if (rowId !== undefined) {
|
|
$deletions.val($deletions.val() + ' ' + rowId);
|
|
|
|
// Notify containing form (if any) about a change
|
|
this.getHtmlElement().trigger('formChange');
|
|
}
|
|
|
|
this.deleteElement(/** @type {jQueryObject} */ ($targetRow));
|
|
|
|
this.availableOptions_ = true;
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated when a request for row appending
|
|
* returns.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} ajaxContext The AJAX request context.
|
|
* @param {Object} jsonData A parsed JSON response object.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
appendRowResponseHandler_ = function(ajaxContext, jsonData) {
|
|
|
|
var processedJsonData = this.handleJson(jsonData), $newRow;
|
|
if (processedJsonData !== false) {
|
|
// Show the new input row; hide the "empty" row
|
|
$newRow = $(processedJsonData.content);
|
|
this.getHtmlElement().find('.empty').hide().before($newRow);
|
|
|
|
// Attach content handlers and focus
|
|
this.attachContentHandlers_($newRow);
|
|
$newRow.addClass('gridRowEdit');
|
|
$newRow.find(':input').not('[type="hidden"]').first().focus();
|
|
|
|
// If this is a select menu listbuilder, load the options
|
|
if (this.sourceType_ == $.pkp.cons.LISTBUILDER_SOURCE_TYPE_SELECT) {
|
|
this.disableControls();
|
|
$.get(this.fetchOptionsUrl_, {},
|
|
this.callbackWrapper(this.fetchOptionsResponseHandler_, null),
|
|
'json');
|
|
} else {
|
|
this.enableControls();
|
|
}
|
|
|
|
this.callFeaturesHook('addElement', $newRow);
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated when a set of options is returned
|
|
* from the server for a new select control.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} ajaxContext The AJAX request context.
|
|
* @param {Object} jsonData A parsed JSON response object.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
fetchOptionsResponseHandler_ = function(ajaxContext, jsonData) {
|
|
|
|
// Find the currently editable select menu and fill
|
|
var pjd = this.handleJson(jsonData),
|
|
$listbuilder = this.getHtmlElement(),
|
|
selectedValues = [],
|
|
$selectInput,
|
|
i, limit,
|
|
$pulldown, $container, optionsCount, j,
|
|
$option,
|
|
label, $optgroup,
|
|
k, optionsInsideGroup, $lastElement;
|
|
|
|
if (pjd !== false) {
|
|
// Get the list of already-selected options, to ensure
|
|
// that we don't offer duplicates.
|
|
$listbuilder.find('.gridCellDisplay :input').each(function(i, selected) {
|
|
selectedValues[i] = $(selected).val();
|
|
});
|
|
|
|
// Get the currently available input row's elements
|
|
$selectInput = $listbuilder.find(
|
|
'.gridRowEdit:visible .selectMenu:input'
|
|
);
|
|
|
|
// For each pulldown (generally 1), add options.
|
|
for (i = 0, limit = $selectInput.length; i < limit; i++) {
|
|
// Fetch some useful properties
|
|
$pulldown = $($selectInput[i]);
|
|
$container = $pulldown.parents('.gridCellContainer');
|
|
|
|
// Add the options, noting the currently selected index
|
|
optionsCount = 0;
|
|
$pulldown.children().empty();
|
|
j = null;
|
|
for (j in pjd.content[i]) {
|
|
// Ignore optgroup labels.
|
|
if (j == $.pkp.cons.LISTBUILDER_OPTGROUP_LABEL) {
|
|
continue;
|
|
}
|
|
|
|
if (typeof(pjd.content[i][j]) == 'object') {
|
|
// Options must go inside an optgroup.
|
|
// Check if we have optgroup label data.
|
|
if (
|
|
pjd.
|
|
content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL] === undefined) {
|
|
continue;
|
|
}
|
|
|
|
if (typeof(
|
|
pjd.content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL]
|
|
) != 'object') {
|
|
|
|
continue;
|
|
}
|
|
|
|
label =
|
|
pjd.content[i][$.pkp.cons.LISTBUILDER_OPTGROUP_LABEL][j];
|
|
if (!label) {
|
|
continue;
|
|
}
|
|
|
|
$optgroup = $('<optgroup></optgroup>');
|
|
$optgroup.attr('label', label);
|
|
$pulldown.append($optgroup);
|
|
|
|
k = null;
|
|
optionsInsideGroup = 0;
|
|
for (k in pjd.content[i][j]) {
|
|
// Populate the optgroup.
|
|
$option = this.populatePulldown_($optgroup,
|
|
selectedValues, pjd.content[i][j][k], k);
|
|
if ($option) {
|
|
optionsCount++;
|
|
optionsInsideGroup++;
|
|
}
|
|
}
|
|
|
|
// Avoid inserting optgroups that have no option.
|
|
if (optionsInsideGroup === 0) {
|
|
$optgroup.remove();
|
|
}
|
|
} else {
|
|
// Just insert the current option.
|
|
$option = this.populatePulldown_($pulldown,
|
|
selectedValues, pjd.content[i][j], j);
|
|
if ($option) {
|
|
optionsCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
$lastElement = $option;
|
|
|
|
// If only one element is available, select it.
|
|
if (optionsCount === 1 && $lastElement) {
|
|
$lastElement.attr('selected', 'selected');
|
|
this.availableOptions_ = false;
|
|
}
|
|
|
|
// If no options are available for this select menu,
|
|
// hide the input to prevent empty dropdown.
|
|
if (optionsCount === 0) {
|
|
$container.find('.gridCellDisplay').show();
|
|
$container.find('.gridCellEdit').hide();
|
|
}
|
|
}
|
|
}
|
|
|
|
this.enableControls();
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Populate the pulldown with options.
|
|
* @private
|
|
* @param {jQueryObject} $element The element to be populated.
|
|
* Can be a pulldown or an optgroup inside the pulldonw.
|
|
* @param {Object} selectedValues Current listbuilder
|
|
* selected values.
|
|
* @param {string} optionText The text to populate the pulldown with.
|
|
* @param {string} optionValue The key to populate the pulldown with.
|
|
* @return {Object|boolean} Return the inserted option or false.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
populatePulldown_ = function(
|
|
$element, selectedValues, optionText, optionValue) {
|
|
|
|
var $container = $element.parents('.gridCellContainer'),
|
|
currentIndex = $container.find('.gridCellDisplay :input').val(),
|
|
isDuplicate = false, k,
|
|
$option;
|
|
|
|
// Check to see if this option is already in the LB.
|
|
if (optionValue != currentIndex) {
|
|
// If it's the current row, don't consider it a duplicate
|
|
for (k = 0; k < selectedValues.length; k++) {
|
|
if (selectedValues[k] == optionValue) {
|
|
isDuplicate = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!isDuplicate) {
|
|
// Create and populate the option node
|
|
$option = $('<option/>');
|
|
$option.attr('value', optionValue);
|
|
$option.text(optionText);
|
|
|
|
if (optionValue == currentIndex) {
|
|
$option.attr('selected', 'selected');
|
|
}
|
|
|
|
$element.append($option);
|
|
return $option;
|
|
} else {
|
|
return false;
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated when a row is clicked for editing
|
|
*
|
|
* @private
|
|
*
|
|
* @param {HTMLElement} callingContext The calling element or object.
|
|
* @param {Event=} opt_event The triggering event (e.g. a click on
|
|
* a button.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.editItemHandler_ =
|
|
function(callingContext, opt_event) {
|
|
|
|
// Close any existing edits if necessary
|
|
this.closeEdits();
|
|
this.editItemCallingContext_ = callingContext;
|
|
|
|
// Show inputs; hide display. IE8 is slow, and it will execute
|
|
// this before the timeout setted in inputBlurHandler_. Insert this
|
|
// code inside a timeout too to avoid closing inputs that are not
|
|
// meant to.
|
|
setTimeout(this.callbackWrapper(function() {
|
|
var $targetRow = $(this.editItemCallingContext_).closest('.gridRow');
|
|
$targetRow.addClass('gridRowEdit');
|
|
$targetRow.find(':input').not('[type="hidden"]').first().focus();
|
|
|
|
// If this is a select menu listbuilder, load the options
|
|
if (this.sourceType_ == $.pkp.cons.LISTBUILDER_SOURCE_TYPE_SELECT) {
|
|
this.disableControls();
|
|
$.get(this.fetchOptionsUrl_, {},
|
|
this.callbackWrapper(this.fetchOptionsResponseHandler_, null),
|
|
'json');
|
|
}
|
|
}), 0);
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Helper function to turn a row into an array of parameters used
|
|
* to generate the DOM representation of that row when bounced
|
|
* off the server.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} $inputs The grid inputs to mine for parameters.
|
|
* @return {Object} A name: value association of relevant parameters.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
buildParamsFromInputs_ = function($inputs) {
|
|
|
|
var params = {};
|
|
$.each($inputs.serializeArray(), function(k, v) {
|
|
var name = v.name,
|
|
value = v.value;
|
|
|
|
params[name] = params[name] === undefined ? value :
|
|
$.isArray(params[name]) ? params[name].concat(value) :
|
|
[params[name], value];
|
|
});
|
|
|
|
return params;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated upon keystroke in a new input field
|
|
* to check for a <cr> (acts as tab-to-next, or if no next, submit).
|
|
*
|
|
* @private
|
|
*
|
|
* @param {HTMLElement} callingContext The calling element or object.
|
|
* @param {Event=} opt_event The triggering event.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
inputKeystrokeHandler_ = function(callingContext, opt_event) {
|
|
|
|
var CR_KEY = 13, TAB_KEY = 9,
|
|
$target, $row, $inputs, i;
|
|
|
|
if (opt_event.which == CR_KEY) {
|
|
$target = $(callingContext);
|
|
$row = $target.parents('.gridRow');
|
|
$inputs = $row.find(':input:visible');
|
|
i = $inputs.index($target);
|
|
if ($inputs.length == i + 1) {
|
|
this.saveRow($row);
|
|
return false; // Prevent default
|
|
} else {
|
|
// Not the last field. Tab to the next.
|
|
$inputs[i + 1].focus();
|
|
return false; // Prevent default
|
|
}
|
|
}
|
|
return true;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback that will be activated when a new/modifying row's input
|
|
* field is blurred to check whether or not to save the row.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {HTMLElement} callingContext The calling element or object.
|
|
* @param {Event=} opt_event The triggering event.
|
|
* @return {boolean} Should return false to stop event processing.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
inputBlurHandler_ = function(callingContext, opt_event) {
|
|
|
|
// Flag currently selected input using a CSS class. (Don't
|
|
// want to pass it into the closure because of the IE memory
|
|
// leak bug.)
|
|
$(callingContext).closest('.gridRow').addClass('editingRowPlaceholder');
|
|
|
|
// Check to see whether the row has lost focus after this event has
|
|
// been processed.
|
|
setTimeout(this.callbackWrapper(function() {
|
|
var $editingRow = $('.editingRowPlaceholder'),
|
|
found = false;
|
|
$editingRow.find(':input').each(function(index, elem) {
|
|
if (elem === document.activeElement) {
|
|
found = true;
|
|
}
|
|
});
|
|
|
|
// Clean up extra placeholder class.
|
|
$editingRow.removeClass('editingRowPlaceholder');
|
|
|
|
// If the focused element isn't within the current row, save.
|
|
if (!found) {
|
|
this.closeEdits();
|
|
}
|
|
}), 0);
|
|
|
|
return true;
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback to replace a grid row's content.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} ajaxContext The AJAX request context.
|
|
* @param {Object} jsonData A parsed JSON response object.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
saveRowResponseHandler_ = function(ajaxContext, jsonData) {
|
|
|
|
var processedJsonData = this.handleJson(jsonData),
|
|
$newContent, rowId;
|
|
|
|
if (processedJsonData !== false) {
|
|
// Unfortunately we can't use a closure to get this from
|
|
// the calling context. Use a class flag "saveRowResponsePlaceholder".
|
|
// (Risks IE closure/DOM element memory leak.)
|
|
$newContent = $(processedJsonData.content);
|
|
|
|
// Store current row id.
|
|
rowId = /** @type {string} */ (this.getHtmlElement()
|
|
.find('.saveRowResponsePlaceholder').attr('id'));
|
|
|
|
// Add to the DOM
|
|
this.getHtmlElement().find('.saveRowResponsePlaceholder').
|
|
replaceWith($newContent);
|
|
|
|
// Make sure row id won't change.
|
|
$newContent.attr('id', rowId);
|
|
|
|
// Attach handlers for content manipulation
|
|
this.attachContentHandlers_($newContent);
|
|
|
|
this.callFeaturesHook('replaceElement', $newContent);
|
|
}
|
|
|
|
// Ensure that containing forms are notified of the changed data
|
|
this.getHtmlElement().trigger('formChange');
|
|
|
|
this.enableControls();
|
|
};
|
|
|
|
|
|
/**
|
|
* Callback after a save response returns from the server.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} ajaxContext The AJAX request context.
|
|
* @param {Object} jsonData A parsed JSON response object.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
saveResponseHandler_ = function(ajaxContext, jsonData) {
|
|
|
|
// Noop
|
|
};
|
|
|
|
|
|
/**
|
|
* Attach content handlers to all "click-to-edit" content within
|
|
* the provided context.
|
|
*
|
|
* @private
|
|
*
|
|
* @param {Object} $context The JQuery object to search for attachables.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.prototype.
|
|
attachContentHandlers_ = function($context) {
|
|
|
|
// Attach click handler for text fields and select menus
|
|
$context.find('.gridCellDisplay').click(
|
|
this.callbackWrapper(this.editItemHandler_));
|
|
|
|
// Attach keypress handler for text fields
|
|
$context.find(':input')
|
|
.keypress(this.callbackWrapper(this.inputKeystrokeHandler_))
|
|
.blur(this.callbackWrapper(this.inputBlurHandler_));
|
|
|
|
// Attach deletion handler
|
|
$context.find('.pkp_linkaction_delete').click(
|
|
this.callbackWrapper(this.deleteItemHandler_));
|
|
};
|
|
|
|
|
|
/**
|
|
* Save the Listbuilder's contents upon a "form submitted" event.
|
|
* @private
|
|
*
|
|
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
|
* that triggered the event.
|
|
* @param {Event} event The event.
|
|
* @return {boolean} False if the form submission should abort.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.formSubmitHandler_ = function(callingForm, event) {
|
|
|
|
// Save the contents
|
|
this.save();
|
|
|
|
// Prevent the submission of LB elements to the parent form
|
|
// (except potentially for :input[name='getSaveFieldName()'])
|
|
this.getHtmlElement().find('.gridRow :input').attr('disabled', 'disabled');
|
|
|
|
// Continue the default (form submit) behavior
|
|
return true;
|
|
};
|
|
|
|
|
|
/**
|
|
* Enable deactivated inputs.
|
|
* @private
|
|
*
|
|
* @param {$.pkp.controllers.form.AjaxFormHandler} callingForm The form
|
|
* that triggered the event.
|
|
* @param {Event} event The event.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.formSubmittedHandler_ = function(callingForm, event) {
|
|
|
|
this.getHtmlElement().find('.gridRow :input').removeAttr('disabled');
|
|
};
|
|
|
|
|
|
/**
|
|
* Disable the add_* links and show the spinner before making AJAX calls.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.disableControls = function() {
|
|
|
|
this.getHtmlElement().
|
|
find('span[class="options"] > a[id*="addItem"]').unbind('mousedown');
|
|
|
|
this.getHtmlElement().find('span[class="options"] > a[id*="addItem"]')
|
|
.mousedown(function() {return false;});
|
|
this.getHtmlElement().find('.h3').addClass('spinner');
|
|
};
|
|
|
|
|
|
/**
|
|
* Re-enable the add_* links and hide the spinner.
|
|
*/
|
|
$.pkp.controllers.listbuilder.ListbuilderHandler.
|
|
prototype.enableControls = function() {
|
|
// rebind our 'click' handler so we can add another item
|
|
// if needed
|
|
this.getHtmlElement().find('span[class="options"] > a[id*="addItem"]').
|
|
mousedown(this.callbackWrapper(this.addItemHandler_));
|
|
this.getHtmlElement().find('.h3').removeClass('spinner');
|
|
};
|
|
|
|
|
|
}(jQuery));
|