first commit
This commit is contained in:
@@ -0,0 +1,856 @@
|
||||
/**
|
||||
* @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));
|
||||
Reference in New Issue
Block a user