640 lines
16 KiB
JavaScript
640 lines
16 KiB
JavaScript
/**
|
|
* @file js/classes/features/OrderItemsFeature.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 OrderItemsFeature
|
|
* @ingroup js_classes_features
|
|
*
|
|
* @brief Base feature class for ordering grid items.
|
|
*/
|
|
(function($) {
|
|
|
|
|
|
/**
|
|
* @constructor
|
|
*
|
|
* @param {jQueryObject} gridHandler The handler of
|
|
* the grid element that this feature is attached to.
|
|
* @param {Object} options Configuration of this feature.
|
|
* @extends $.pkp.classes.features.Feature
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature =
|
|
function(gridHandler, options) {
|
|
this.parent(gridHandler, options);
|
|
|
|
this.$orderButton_ = $('.pkp_linkaction_orderItems',
|
|
this.getGridHtmlElement());
|
|
this.$finishControl_ = $('.order_finish_controls', this.getGridHtmlElement());
|
|
|
|
if (this.$orderButton_.length === 0) {
|
|
// No order button, it will always stay in ordering mode.
|
|
this.isOrdering = true;
|
|
}
|
|
|
|
this.itemsOrder = [];
|
|
};
|
|
$.pkp.classes.Helper.inherits(
|
|
$.pkp.classes.features.OrderItemsFeature,
|
|
$.pkp.classes.features.Feature);
|
|
|
|
|
|
//
|
|
// Protected properties
|
|
//
|
|
/**
|
|
* Item sequence.
|
|
* @protected
|
|
* @type {Array}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.itemsOrder = null;
|
|
|
|
|
|
/**
|
|
* Flag to control if user is ordering items.
|
|
* @protected
|
|
* @type {boolean}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.isOrdering = false;
|
|
|
|
|
|
//
|
|
// Private properties.
|
|
//
|
|
/**
|
|
* Initiate ordering state button.
|
|
* @private
|
|
* @type {jQueryObject}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.$orderButton_ = null;
|
|
|
|
|
|
/**
|
|
* Cancel ordering state button.
|
|
* @private
|
|
* @type {jQueryObject}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.$cancelButton_ = null;
|
|
|
|
|
|
/**
|
|
* Save ordering state button.
|
|
* @private
|
|
* @type {jQueryObject}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.$saveButton_ = null;
|
|
|
|
|
|
/**
|
|
* Ordering finish control.
|
|
* @private
|
|
* @type {jQueryObject}
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.$finishControl_ = null;
|
|
|
|
|
|
//
|
|
// Getters and setters.
|
|
//
|
|
/**
|
|
* Get the order button.
|
|
* @return {jQueryObject} The order button JQuery object.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getOrderButton =
|
|
function() {
|
|
return this.$orderButton_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the finish control.
|
|
* @return {jQueryObject} The JQuery "finish" control.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getFinishControl =
|
|
function() {
|
|
return this.$finishControl_;
|
|
};
|
|
|
|
|
|
/**
|
|
* Get save order button.
|
|
*
|
|
* @return {jQueryObject} The "save order" JQuery object.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getSaveOrderButton =
|
|
function() {
|
|
return this.getFinishControl().find('.saveButton');
|
|
};
|
|
|
|
|
|
/**
|
|
* Get cancel order link.
|
|
*
|
|
* @return {jQueryObject} The "cancel order" JQuery control.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getCancelOrderButton =
|
|
function() {
|
|
return this.getFinishControl().find('.cancelFormButton');
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the move item row action element selector.
|
|
* @return {string} Return the element selector.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
getMoveItemRowActionSelector = function() {
|
|
return '.orderable .pkp_linkaction_moveItem';
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the css classes used to stylize the ordering items.
|
|
* @return {string} CSS classes.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getMoveItemClasses =
|
|
function() {
|
|
return 'pkp_helpers_moveicon ordering';
|
|
};
|
|
|
|
|
|
//
|
|
// Public template methods.
|
|
//
|
|
/**
|
|
* Called every time user start dragging an item.
|
|
* @param {jQueryObject} contextElement The element this event occurred for.
|
|
* @param {Event} event The drag/drop event.
|
|
* @param {Object} ui Object with data related to the event elements.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.dragStartCallback =
|
|
function(contextElement, event, ui) {
|
|
// The default implementation does nothing.
|
|
};
|
|
|
|
|
|
/**
|
|
* Called every time user stop dragging an item.
|
|
* @param {jQueryObject} contextElement The element this event occurred for.
|
|
* @param {Event} event The drag/drop event.
|
|
* @param {Object} ui Object with data related to the event elements.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.dragStopCallback =
|
|
function(contextElement, event, ui) {
|
|
// The default implementation does nothing.
|
|
};
|
|
|
|
|
|
/**
|
|
* Called every time sequence is changed.
|
|
* @param {jQueryObject} contextElement The element this event occurred for.
|
|
* @param {Event} event The drag/drop event.
|
|
* @param {Object} ui Object with data related to the event elements.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.updateOrderCallback =
|
|
function(contextElement, event, ui) {
|
|
// The default implementation does nothing.
|
|
};
|
|
|
|
|
|
//
|
|
// Extended methods from Feature
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.init =
|
|
function() {
|
|
|
|
this.addOrderingClassToRows();
|
|
this.toggleMoveItemRowAction(this.isOrdering);
|
|
this.getGridHtmlElement().find('div.order_message').hide();
|
|
|
|
this.toggleOrderLink_();
|
|
if (this.isOrdering) {
|
|
this.setupSortablePlugin();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.addFeatureHtml =
|
|
function($gridElement, options) {
|
|
var castOptions = /** @type {{orderFinishControls: string?,
|
|
orderMessage: string?}} */ (options),
|
|
$orderFinishControls, orderMessageHtml, $gridRows;
|
|
if (castOptions.orderFinishControls !== undefined) {
|
|
$orderFinishControls = $(castOptions.orderFinishControls);
|
|
$gridElement.find('table').last().after($orderFinishControls);
|
|
$orderFinishControls.hide();
|
|
}
|
|
|
|
if (castOptions.orderMessage !== undefined) {
|
|
orderMessageHtml = castOptions.orderMessage;
|
|
$gridRows = $gridElement.find('.gridRow').filter(function(index, element) {
|
|
return !Boolean($(this).find('a.pkp_linkaction_moveItem').length);
|
|
});
|
|
$gridRows.find('td:first-child').prepend(orderMessageHtml);
|
|
}
|
|
|
|
this.updateOrderLinkVisibility_();
|
|
};
|
|
|
|
|
|
//
|
|
// Protected template methods.
|
|
//
|
|
/**
|
|
* Add orderable class to grid rows.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.addOrderingClassToRows =
|
|
function() {
|
|
// Add ordering class to grid rows.
|
|
var $gridRows = this.gridHandler.getRows().filter(function(index, element) {
|
|
return $(this).find('a.pkp_linkaction_moveItem').length;
|
|
});
|
|
$gridRows.addClass('orderable');
|
|
};
|
|
|
|
|
|
/**
|
|
* Setup the sortable plugin. Must be implemented in subclasses.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.setupSortablePlugin =
|
|
function() {
|
|
// Default implementation does nothing.
|
|
};
|
|
|
|
|
|
/**
|
|
* Called every time storeOrder is called. This is a chance to subclasses
|
|
* execute operations with each row that has their sequence being saved.
|
|
* @param {number} index The current row index position inside the rows
|
|
* jQuery object.
|
|
* @param {jQueryObject} $row Row for which to store the sequence.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.storeRowOrder =
|
|
function(index, $row) {
|
|
// The default implementation does nothing.
|
|
};
|
|
|
|
|
|
//
|
|
// Protected methods.
|
|
//
|
|
/**
|
|
* Initiate ordering button click event handler.
|
|
* @return {boolean} Always returns false.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.clickOrderHandler =
|
|
function() {
|
|
this.gridHandler.hideAllVisibleRowActions();
|
|
this.storeOrder(this.gridHandler.getRows());
|
|
this.toggleState(true);
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Save order handler.
|
|
* @return {boolean} Return false to stop click event processing.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.saveOrderHandler =
|
|
function() {
|
|
var $rows;
|
|
|
|
this.gridHandler.updateControlRowsPosition();
|
|
this.unbindOrderFinishControlsHandlers_();
|
|
$rows = this.gridHandler.getRows();
|
|
this.storeOrder($rows);
|
|
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Cancel ordering action click event handler.
|
|
* @return {boolean} Always returns false.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.cancelOrderHandler =
|
|
function() {
|
|
this.gridHandler.resequenceRows(this.itemsOrder);
|
|
this.toggleState(false);
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* Execute all operations necessary to change the state of the
|
|
* ordering process (enabled or disabled).
|
|
* @param {boolean} isOrdering Is ordering process active?
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleState =
|
|
function(isOrdering) {
|
|
this.isOrdering = isOrdering;
|
|
this.toggleGridLinkActions_();
|
|
this.toggleOrderLink_();
|
|
this.toggleFinishControl_();
|
|
this.toggleItemsDragMode();
|
|
this.setupSortablePlugin();
|
|
this.setupNonOrderableMessage_();
|
|
};
|
|
|
|
|
|
/**
|
|
* Set rows sequence store, using
|
|
* the sequence of the passed items.
|
|
*
|
|
* @param {jQueryObject} $rows The rows to be used to get the sequence
|
|
* information.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.storeOrder =
|
|
function($rows) {
|
|
var index, limit, $row, elementId;
|
|
this.itemsOrder = [];
|
|
for (index = 0, limit = $rows.length; index < limit; index++) {
|
|
$row = $($rows[index]);
|
|
elementId = $row.attr('id');
|
|
|
|
this.itemsOrder.push(elementId);
|
|
|
|
// Give a chance to subclasses do extra operations to store
|
|
// the current row order.
|
|
this.storeRowOrder(index, $row);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Enable/disable the items drag mode.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleItemsDragMode =
|
|
function() {
|
|
var isOrdering = this.isOrdering,
|
|
$rows = this.gridHandler.getRows(),
|
|
$orderableRows = $rows.filter('.orderable'),
|
|
moveClasses = this.getMoveItemClasses();
|
|
|
|
if (isOrdering) {
|
|
$orderableRows.addClass(moveClasses);
|
|
} else {
|
|
$orderableRows.removeClass(moveClasses);
|
|
}
|
|
|
|
this.toggleMoveItemRowAction(isOrdering);
|
|
};
|
|
|
|
|
|
/**
|
|
* Apply (disabled or enabled) the sortable plugin on passed elements.
|
|
* @param {jQueryObject} $container The element that contain all the orderable
|
|
* items.
|
|
* @param {string} itemsSelector The jQuery selector for orderable items.
|
|
* @param {Object?} extraParams Optional set of extra parameters for sortable.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.applySortPlgOnElements =
|
|
function($container, itemsSelector, extraParams) {
|
|
var isOrdering = this.isOrdering,
|
|
dragStartCallback = this.gridHandler.callbackWrapper(
|
|
this.dragStartCallback, this),
|
|
dragStopCallback = this.gridHandler.callbackWrapper(
|
|
this.dragStopCallback, this),
|
|
orderItemCallback = this.gridHandler.callbackWrapper(
|
|
this.updateOrderCallback, this),
|
|
config = {
|
|
disabled: !isOrdering,
|
|
items: itemsSelector,
|
|
activate: dragStartCallback,
|
|
deactivate: dragStopCallback,
|
|
update: orderItemCallback,
|
|
tolerance: 'pointer'};
|
|
|
|
if (typeof extraParams === 'object') {
|
|
config = $.extend(true, config, extraParams);
|
|
}
|
|
|
|
$container.sortable(config);
|
|
};
|
|
|
|
|
|
/**
|
|
* Get the data element id of all rows inside the passed
|
|
* container, in the current order.
|
|
* @param {jQueryObject} $rowsContainer The element that contains the rows
|
|
* that will be used to retrieve the id.
|
|
* @return {Array} A sequence array with data element ids as values.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.getRowsDataId =
|
|
function($rowsContainer) {
|
|
var index, rowDataIds = [], $row, rowDataId;
|
|
for (index in this.itemsOrder) {
|
|
$row = $('#' + this.itemsOrder[index], $rowsContainer);
|
|
if ($row.length < 1) {
|
|
continue;
|
|
}
|
|
rowDataId = this.gridHandler.getRowDataId($row);
|
|
rowDataIds.push(rowDataId);
|
|
}
|
|
|
|
return rowDataIds;
|
|
};
|
|
|
|
|
|
/**
|
|
* Show/hide the move item row action (position left).
|
|
* @param {boolean} enable New enable state.
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleMoveItemRowAction =
|
|
function(enable) {
|
|
var $grid = this.getGridHtmlElement(),
|
|
$actionsContainer = $('div.row_actions', $grid),
|
|
allLinksButMoveItemSelector = 'a:not(' +
|
|
this.getMoveItemRowActionSelector() + ')',
|
|
$actions = $actionsContainer.find(allLinksButMoveItemSelector),
|
|
$moveItemRowAction = $(this.getMoveItemRowActionSelector(), $grid),
|
|
$rowActionsContainer, $rowActions;
|
|
|
|
if (enable) {
|
|
$actions.addClass('pkp_helpers_display_none');
|
|
$moveItemRowAction.show();
|
|
// Make sure row actions div is visible.
|
|
this.gridHandler.showRowActionsDiv();
|
|
} else {
|
|
$actions.removeClass('pkp_helpers_display_none');
|
|
|
|
$rowActionsContainer = $('.gridRow div.row_actions', $grid);
|
|
$rowActions = $rowActionsContainer.
|
|
find(allLinksButMoveItemSelector);
|
|
if ($rowActions.length === 0) {
|
|
// No link action to show, hide row actions div.
|
|
this.gridHandler.hideRowActionsDiv();
|
|
}
|
|
$moveItemRowAction.hide();
|
|
}
|
|
};
|
|
|
|
|
|
//
|
|
// Hooks implementation.
|
|
//
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.addElement =
|
|
function($element) {
|
|
this.addOrderingClassToRows();
|
|
this.toggleItemsDragMode();
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.replaceElement =
|
|
function($content) {
|
|
this.addOrderingClassToRows();
|
|
this.toggleItemsDragMode();
|
|
return false;
|
|
};
|
|
|
|
|
|
/**
|
|
* @inheritDoc
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
replaceElementResponseHandler = function(handledJsonData) {
|
|
this.updateOrderLinkVisibility_();
|
|
this.setupNonOrderableMessage_();
|
|
return false;
|
|
};
|
|
|
|
|
|
//
|
|
// Private helper methods.
|
|
//
|
|
/**
|
|
* Make sure that the order action visibility state is correct,
|
|
* based on the grid rows number.
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
updateOrderLinkVisibility_ = function() {
|
|
var $orderLink = $('.pkp_linkaction_orderItems', this.getGridHtmlElement());
|
|
if (this.gridHandler.getRows().length <= 1) {
|
|
$orderLink.hide();
|
|
} else {
|
|
$orderLink.show();
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Set the state of the grid link actions, based on current ordering state.
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleGridLinkActions_ =
|
|
function() {
|
|
var isOrdering = this.isOrdering,
|
|
// We want to enable/disable all link actions, except this
|
|
// features controls.
|
|
$gridLinkActions = $('.pkp_controllers_linkAction',
|
|
this.getGridHtmlElement()).not(
|
|
this.getMoveItemRowActionSelector()).not(
|
|
this.getOrderButton()).not(
|
|
this.getFinishControl().find('*'));
|
|
|
|
this.gridHandler.changeLinkActionsState(!isOrdering, $gridLinkActions);
|
|
};
|
|
|
|
|
|
/**
|
|
* Enable/disable the order link action.
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleOrderLink_ =
|
|
function() {
|
|
if (this.isOrdering) {
|
|
this.$orderButton_.unbind('click');
|
|
this.$orderButton_.attr('disabled', 'disabled');
|
|
} else {
|
|
var clickHandler = this.gridHandler.callbackWrapper(
|
|
this.clickOrderHandler, this);
|
|
this.$orderButton_.click(clickHandler);
|
|
this.$orderButton_.removeAttr('disabled');
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Show/hide the ordering process finish control, based
|
|
* on the current ordering state.
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.toggleFinishControl_ =
|
|
function() {
|
|
if (this.isOrdering) {
|
|
this.bindOrderFinishControlsHandlers_();
|
|
this.getFinishControl().slideDown(300);
|
|
} else {
|
|
this.unbindOrderFinishControlsHandlers_();
|
|
this.getFinishControl().slideUp(300);
|
|
}
|
|
};
|
|
|
|
|
|
/**
|
|
* Bind event handlers to the controls that finish the
|
|
* ordering action (save and cancel).
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
bindOrderFinishControlsHandlers_ = function() {
|
|
var $saveButton = this.getSaveOrderButton(),
|
|
$cancelLink = this.getCancelOrderButton(),
|
|
cancelLinkHandler = this.gridHandler.callbackWrapper(
|
|
this.cancelOrderHandler, this),
|
|
saveButtonHandler = this.gridHandler.callbackWrapper(
|
|
this.saveOrderHandler, this);
|
|
|
|
$saveButton.click(saveButtonHandler);
|
|
$cancelLink.click(cancelLinkHandler);
|
|
};
|
|
|
|
|
|
/**
|
|
* Unbind event handlers from the controls that finish the
|
|
* ordering action (save and cancel).
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
unbindOrderFinishControlsHandlers_ = function() {
|
|
|
|
this.getSaveOrderButton().unbind('click');
|
|
this.getCancelOrderButton().unbind('click');
|
|
};
|
|
|
|
|
|
/**
|
|
* Toggle hover action to show message for non orderable
|
|
* grid rows.
|
|
* @private
|
|
*/
|
|
$.pkp.classes.features.OrderItemsFeature.prototype.
|
|
setupNonOrderableMessage_ = function() {
|
|
if (this.isOrdering) {
|
|
this.gridHandler.getRows().hover(function() {
|
|
$(this).find('div.order_message').toggle();
|
|
});
|
|
} else {
|
|
this.gridHandler.getRows().unbind('mouseenter mouseleave');
|
|
}
|
|
};
|
|
|
|
|
|
}(jQuery));
|