Subida del módulo y tema de PrestaShop

This commit is contained in:
Kaloyan
2026-04-09 18:31:51 +02:00
parent 12c253296f
commit 16b3ff9424
39262 changed files with 7418797 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class DeleteCategoriesBulkActionExtension handles submitting of row action
*/
export default class DeleteCategoriesBulkActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid.getContainer().on('click', GridMap.bulks.deleteCategories, (event) => {
event.preventDefault();
const submitUrl = $(event.currentTarget).data('categories-delete-url');
const $deleteCategoriesModal = $(
GridMap.bulks.deleteCategoriesModal(grid.getId()),
);
$deleteCategoriesModal.modal('show');
$deleteCategoriesModal.on(
'click',
GridMap.bulks.submitDeleteCategories,
() => {
const $checkboxes = grid
.getContainer()
.find(GridMap.bulks.checkedCheckbox);
const $categoriesToDeleteInputBlock = $(
GridMap.bulks.categoriesToDelete,
);
$checkboxes.each((i, element) => {
const $checkbox = $(element);
const categoryInput = $categoriesToDeleteInputBlock
.data('prototype')
.replace(/__name__/g, $checkbox.val());
const $input = $($.parseHTML(categoryInput)[0]);
$input.val(<string>$checkbox.val());
$categoriesToDeleteInputBlock.append($input);
});
const $form = $deleteCategoriesModal.find('form');
$form.attr('action', submitUrl);
$form.submit();
},
);
});
}
}

View File

@@ -0,0 +1,63 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Handles bulk delete for "Customers" grid.
*/
export default class DeleteCustomersBulkActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid.getContainer().on('click', GridMap.bulks.deleteCustomers, (event) => {
event.preventDefault();
const submitUrl = $(event.currentTarget).data('customers-delete-url');
const $modal = $(GridMap.bulks.deleteCustomerModal(grid.getId()));
$modal.modal('show');
$modal.on('click', GridMap.bulks.submitDeleteCustomers, () => {
const $selectedCustomerCheckboxes = grid
.getContainer()
.find(GridMap.bulks.checkedCheckbox);
$selectedCustomerCheckboxes.each((i, checkbox) => {
const $input = $(checkbox);
this.addCustomerToDeleteCollectionInput(<number>$input.val());
});
const $form = $modal.find('form');
$form.attr('action', submitUrl);
$form.submit();
});
});
}
/**
* Create input with customer id and add it to delete collection input
*
* @private
*/
private addCustomerToDeleteCollectionInput(customerId: number): void {
const $customersInput = $(GridMap.bulks.customersToDelete);
const customerInput = $customersInput
.data('prototype')
.replace(/__name__/g, customerId);
const $item = $($.parseHTML(customerInput)[0]);
$item.val(customerId);
$customersInput.append($item);
}
}

View File

@@ -0,0 +1,62 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class CategoryDeleteRowActionExtension handles submitting of row action
*/
export default class DeleteCategoryRowActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.rows.categoryDeleteAction, (event) => {
event.preventDefault();
const $deleteCategoriesModal = $(
GridMap.bulks.deleteCategoriesModal(grid.getId()),
);
$deleteCategoriesModal.modal('show');
$deleteCategoriesModal.on(
'click',
GridMap.bulks.submitDeleteCategories,
() => {
const $button = $(event.currentTarget);
const categoryId = $button.data('category-id');
const $categoriesToDeleteInputBlock = $(
GridMap.bulks.categoriesToDelete,
);
const categoryInput = $categoriesToDeleteInputBlock
.data('prototype')
.replace(
/__name__/g,
$categoriesToDeleteInputBlock.children().length,
);
const $item = $($.parseHTML(categoryInput)[0]);
$item.val(categoryId);
$categoriesToDeleteInputBlock.append($item);
const $form = $deleteCategoriesModal.find('form');
$form.attr('action', $button.data('category-delete-url'));
$form.submit();
},
);
});
}
}

View File

@@ -0,0 +1,67 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class DeleteCustomerRowActionExtension handles submitting of row action
*/
export default class DeleteCustomerRowActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.rows.customerDeleteAction, (event) => {
event.preventDefault();
const $deleteCustomersModal = $(
GridMap.bulks.deleteCustomerModal(grid.getId()),
);
$deleteCustomersModal.modal('show');
$deleteCustomersModal.on(
'click',
GridMap.bulks.submitDeleteCustomers,
() => {
const $button = $(event.currentTarget);
const customerId = $button.data('customer-id');
this.addCustomerInput(customerId);
const $form = $deleteCustomersModal.find('form');
$form.attr('action', $button.data('customer-delete-url'));
$form.submit();
},
);
});
}
/**
* Adds input for selected customer to delete form
*
* @param {integer} customerId
*
* @private
*/
private addCustomerInput(customerId: number): void {
const $customersToDeleteInputBlock = $(GridMap.bulks.customersToDelete);
const customerInput = $customersToDeleteInputBlock
.data('prototype')
.replace(/__name__/g, $customersToDeleteInputBlock.children().length);
const $item = $($.parseHTML(customerInput)[0]);
$item.val(customerId);
$customersToDeleteInputBlock.append($item);
}
}

View File

@@ -0,0 +1,36 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class DeleteCustomerRowActionExtension handles submitting of row action
*/
export default class DeleteImageTypeRowActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.rows.imageTypeDeleteAction, (event) => {
event.preventDefault();
const $button = $(event.currentTarget);
const $deleteImageTypeModal = $(GridMap.rows.deleteImageTypeModal(grid.getId()));
$deleteImageTypeModal.modal('show');
$deleteImageTypeModal.on('click', GridMap.rows.submitDeleteImageType, () => {
const $form = $deleteImageTypeModal.find('form');
$form.attr('action', $button.data('delete-url'));
$form.submit();
});
});
}
}

View File

@@ -0,0 +1,103 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
import {ConfirmModal} from '@components/modal';
const {$} = window;
/**
* Class SubmitRowActionExtension handles submitting of row action
*/
export default class SubmitRowActionExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid.getContainer().on('click', '.js-submit-row-action', (event) => {
event.preventDefault();
const $button = $(event.currentTarget);
const confirmMessage = $button.data('confirmMessage');
const confirmTitle = $button.data('title');
const method = $button.data('method');
if (confirmTitle) {
this.showConfirmModal(
$button,
grid,
confirmMessage,
confirmTitle,
method,
);
} else {
// eslint-disable-next-line
if (confirmMessage.length && !window.confirm(confirmMessage)) {
return;
}
this.postForm($button, method);
}
});
}
postForm($button: JQuery, method: string): void {
const isGetOrPostMethod = ['GET', 'POST'].includes(method);
const $form = $('<form>', {
action: $button.data('url'),
method: isGetOrPostMethod ? method : 'POST',
}).appendTo('body');
if (!isGetOrPostMethod) {
$form.append(
$('<input>', {
type: 'hidden',
name: '_method',
value: method,
}),
);
}
$form.submit();
}
/**
* @param {jQuery} $submitBtn
* @param {Grid} grid
* @param {string} confirmMessage
* @param {string} confirmTitle
* @param {string} method
*/
showConfirmModal(
$submitBtn: JQuery,
grid: Grid,
confirmMessage: string,
confirmTitle: string,
method: string,
): void {
const confirmButtonLabel = $submitBtn.data('confirmButtonLabel');
const closeButtonLabel = $submitBtn.data('closeButtonLabel');
const confirmButtonClass = $submitBtn.data('confirmButtonClass');
const modal = new ConfirmModal(
{
id: GridMap.confirmModal(grid.getId()),
confirmTitle,
confirmMessage,
confirmButtonLabel,
closeButtonLabel,
confirmButtonClass,
},
() => this.postForm($submitBtn, method),
);
modal.show();
}
}

View File

@@ -0,0 +1,174 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import ProgressModal from '@components/modal/progress-modal';
import ConfirmModal from '@components/modal/confirm-modal';
import GridMap from '@components/grid/grid-map';
import Router from '@components/router';
import ClickEvent = JQuery.ClickEvent;
const {$} = window;
/**
* Handles submit of grid actions
*/
export default class AjaxBulkActionExtension {
private router = new Router();
/**
* Extend grid with bulk action submitting
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.bulks.ajaxAction, (event: ClickEvent) => {
const $ajaxButton: JQuery<HTMLInputElement> = $<HTMLInputElement>(event.currentTarget);
const $checkboxes: JQuery<HTMLInputElement> = $<HTMLInputElement>(GridMap.bulks.checkedCheckbox);
const selectedIds: string[] = $checkboxes.get().map((checkbox: HTMLInputElement) => checkbox.value);
if (selectedIds.length === 0) {
return;
}
const confirmBulkAction = $ajaxButton.data('confirmBulkAction') ?? true;
if (confirmBulkAction) {
const progressionTitle = $ajaxButton.data('progressTitle');
const closeButtonLabel = $ajaxButton.data('cancelLabel') || 'Cancel';
const confirmTitle = $ajaxButton.data('confirmTitle') || 'Apply modifications';
const bulkAction = $ajaxButton.data('bulkAction') ?? 'bulk-action';
const confirmModal = new ConfirmModal({
id: GridMap.actions.ajaxBulkActionConfirmModal(grid.id, bulkAction),
modalTitle: confirmTitle,
closeButtonLabel,
confirmMessage: progressionTitle.replace('%total%', selectedIds.length),
confirmButtonLabel: confirmTitle,
confirmCallback: () => {
this.submitForm(grid, $<HTMLInputElement>(event.currentTarget), selectedIds);
},
});
confirmModal.show();
} else {
this.submitForm(grid, $<HTMLInputElement>(event.currentTarget), selectedIds);
}
});
}
private async submitForm(grid: Grid, $ajaxButton: JQuery<HTMLInputElement>, selectedIds: string[]): Promise<void> {
const bulkChunkSize = $ajaxButton.data('bulkChunkSize') ?? 10;
const reloadAfterBulk = $ajaxButton.data('reloadAfterBulk') ?? true;
const bulkAction = $ajaxButton.data('bulkAction') ?? 'bulk-action';
const progressionTitle = $ajaxButton.data('progressTitle');
const progressionMessage = $ajaxButton.data('progressMessage');
const closeLabel = $ajaxButton.data('closeLabel');
const abortProcessingLabel = $ajaxButton.data('stopProcessing');
const errorsMessage = $ajaxButton.data('errorsMessage');
const backToProcessingLabel = $ajaxButton.data('backToProcessing');
const downloadErrorLogLabel = $ajaxButton.data('downloadErrorLog');
const viewErrorLogLabel = $ajaxButton.data('viewErrorLog');
const viewErrorTitle = $ajaxButton.data('viewErrorTitle');
const abortController = new AbortController();
const modal = new ProgressModal({
id: GridMap.actions.ajaxBulkActionProgressModal(grid.id, bulkAction),
abortCallback: () => {
stopProcess = true;
abortController.abort();
},
closeCallback: () => {
if (reloadAfterBulk) {
$<HTMLInputElement>(GridMap.bulks.checkedCheckbox).filter(':checked').prop('checked', false);
window.location.reload();
}
},
progressionTitle,
progressionMessage,
closeLabel,
abortProcessingLabel,
errorsMessage,
backToProcessingLabel,
downloadErrorLogLabel,
viewErrorLogLabel,
viewErrorTitle,
total: selectedIds.length,
});
modal.show();
let stopProcess = false;
let doneCount = 0;
while (selectedIds.length) {
const chunkIds: string[] = selectedIds.splice(0, bulkChunkSize);
if (stopProcess) {
break;
}
let data: Record<string, any>;
try {
// eslint-disable-next-line no-await-in-loop
const response = await this.callAjaxAction($ajaxButton, chunkIds, abortController.signal);
// eslint-disable-next-line no-await-in-loop
data = await response.json();
} catch (e: any) {
data = {error: `Something went wrong with IDs ${chunkIds.join(', ')}: ${e.message ?? ''}`};
}
doneCount += chunkIds.length;
modal.updateProgress(doneCount);
if (!data.success) {
if (data.errors && Array.isArray(data.errors)) {
data.errors.forEach((error:string) => {
modal.addError(error);
});
} else {
modal.addError(data.errors ?? data.error ?? data.message);
}
}
}
modal.completeProgress();
}
private callAjaxAction($ajaxButton: JQuery<HTMLInputElement>, chunkIds: string[], abortSignal: AbortSignal): Promise<Response> {
const requestParamName: string = $ajaxButton.data('requestParamName') ?? 'bulk_ids';
const routeParams: Record<string, any> = $ajaxButton.data('routeParams') ?? {};
const routeMethod: string = $ajaxButton.data('routeMethod') ?? 'POST';
const formData: FormData = new FormData();
chunkIds.forEach((chunkId: string, index: number) => {
formData.append(`${requestParamName}[${index}]`, chunkId);
});
let requestMethod: string;
// For PATCH and DELETE request we use a POST request but we use the _method for Symfony to handle it
switch (routeMethod.toUpperCase()) {
case 'PATCH':
case 'DELETE':
requestMethod = 'POST';
break;
default:
requestMethod = routeMethod;
break;
}
return fetch(this.router.generate($ajaxButton.data('ajax-url'), routeParams), {
method: requestMethod,
body: formData,
headers: {
_method: routeMethod,
},
signal: abortSignal,
});
}
}

View File

@@ -0,0 +1,115 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class BulkActionSelectCheckboxExtension
*/
export default class BulkActionCheckboxExtension {
/**
* Extend grid with bulk action checkboxes handling functionality
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
this.handleBulkActionCheckboxStatus(grid);
this.handleBulkActionCheckboxSelect(grid);
this.handleBulkActionSelectAllCheckbox(grid);
}
/**
* Disable/Enable "Select all" button in the grid
*
* @param {Grid} grid
*
* @private
*/
private handleBulkActionCheckboxStatus(grid: Grid) {
const gridBulkActionSelectAll = grid.getContainer().find(GridMap.bulks.actionSelectAll);
gridBulkActionSelectAll.prop(
'disabled',
grid.getContainer().find(GridMap.bulks.bulkActionCheckbox).length === 0,
);
}
/**
* Handles "Select all" button in the grid
*
* @param {Grid} grid
*
* @private
*/
private handleBulkActionSelectAllCheckbox(grid: Grid) {
grid.getContainer().on('change', GridMap.bulks.actionSelectAll, (e) => {
const $checkbox = $(e.currentTarget);
const isChecked = $checkbox.is(':checked');
if (isChecked) {
this.enableBulkActionsBtn(grid);
} else {
this.disableBulkActionsBtn(grid);
}
grid
.getContainer()
.find(GridMap.bulks.bulkActionCheckbox)
.prop('checked', isChecked);
});
}
/**
* Handles each bulk action checkbox select in the grid
*
* @param {Grid} grid
*
* @private
*/
private handleBulkActionCheckboxSelect(grid: Grid) {
grid.getContainer().on('change', GridMap.bulks.bulkActionCheckbox, () => {
const checkedRowsCount = grid
.getContainer()
.find(GridMap.bulks.checkedCheckbox).length;
if (checkedRowsCount > 0) {
this.enableBulkActionsBtn(grid);
} else {
this.disableBulkActionsBtn(grid);
}
});
}
/**
* Enable bulk actions button
*
* @param {Grid} grid
*
* @private
*/
private enableBulkActionsBtn(grid: Grid): void {
grid
.getContainer()
.find(GridMap.bulks.bulkActionBtn)
.prop('disabled', false);
}
/**
* Disable bulk actions button
*
* @param {Grid} grid
*
* @private
*/
private disableBulkActionsBtn(grid: Grid): void {
grid
.getContainer()
.find(GridMap.bulks.bulkActionBtn)
.prop('disabled', true);
}
}

View File

@@ -0,0 +1,70 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
import Router from '../../router';
const {$} = window;
/**
* Class BulkOpenTabsExtension
*/
export default class BulkOpenTabsExtension {
router: Router;
constructor() {
this.router = new Router();
}
/**
* Extend grid with bulk action open tabs
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.bulks.openTabsBtn, (event: JQueryEventObject) => {
this.openTabs(event, grid);
});
}
/**
* Handle bulk action opening tabs
*
* @param {Event} event
* @param {Grid} grid
*
* @private
*/
openTabs(event: JQueryEventObject, grid: Grid): void {
const $submitBtn = $(event.currentTarget);
const route = $submitBtn.data('route');
const routeParamName = $submitBtn.data('routeParamName');
const tabsBlockedMessage = $submitBtn.data('tabsBlockedMessage');
const $checkboxes = grid.getContainer().find(GridMap.bulks.checkedCheckbox);
let allTabsOpened = true;
$checkboxes.each((i, element) => {
const $checkbox = $(element);
const routeParams = {};
// @ts-ignore
routeParams[routeParamName] = $checkbox.val();
const handle = window.open(this.router.generate(route, routeParams));
if (handle) {
handle.blur();
window.focus();
} else {
allTabsOpened = false;
}
if (!allTabsOpened) {
alert(tabsBlockedMessage);
}
});
}
}

View File

@@ -0,0 +1,91 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* This extension enables submit functionality of the choice fields in grid.
*
* Usage of the extension:
*
* const myGrid = new Grid('myGrid');
* myGrid.addExtension(new ChoiceExtension());
*
*/
export default class ChoiceExtension {
lockArray: Array<string>;
constructor() {
this.lockArray = [];
}
extend(grid: Grid): void {
const $choiceOptionsContainer = grid
.getContainer()
.find(GridMap.bulks.choiceOptions);
$choiceOptionsContainer.find(GridMap.dropdownItem).on('click', (e) => {
e.preventDefault();
const $button = $(e.currentTarget);
const $parent = $button.closest(GridMap.bulks.choiceOptions);
const url = $parent.data('url');
this.submitForm(url, $button);
});
}
/**
* Submits the form.
* @param {string} url
* @param {jQuery} $button
* @private
*/
private submitForm(url: string, $button: JQuery) {
const selectedStatusId = $button.data('value');
if (this.isLocked(url)) {
return;
}
const $form = $('<form>', {
action: url,
method: 'POST',
}).append(
$('<input>', {
name: 'value',
value: selectedStatusId,
type: 'hidden',
}),
);
$form.appendTo('body');
$form.submit();
this.lock(url);
}
/**
* Checks if current url is being used at the moment.
*
* @param url
* @return {boolean}
*
* @private
*/
private isLocked(url: string): boolean {
return this.lockArray.includes(url);
}
/**
* Locks the current url so it cant be used twice to execute same request
* @param url
* @private
*/
private lock(url: string): void {
this.lockArray.push(url);
}
}

View File

@@ -0,0 +1,52 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class ReloadListExtension extends grid with "Column toggling" feature
*/
export default class ColumnTogglingExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
const $table = grid.getContainer().find(GridMap.table);
$table.find(GridMap.togglableRow).on('click', (e) => {
e.preventDefault();
this.toggleValue($(e.delegateTarget));
});
}
/**
* @param {jQuery} row
* @private
*/
private toggleValue(row: JQuery) {
const toggleUrl = row.data('toggleUrl');
this.submitAsForm(toggleUrl);
}
/**
* Submits request url as form
*
* @param {string} toggleUrl
* @private
*/
private submitAsForm(toggleUrl: string) {
const $form = $('<form>', {
action: toggleUrl,
method: 'POST',
}).appendTo('body');
$form.submit();
}
}

View File

@@ -0,0 +1,169 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import 'tablednd/dist/jquery.tablednd.min';
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class CategoryPositionExtension extends Grid with reorderable category positions
*/
export default class CategoryPositionExtension {
grid: Grid;
originalPositions: string;
constructor(grid: Grid) {
this.grid = grid;
this.originalPositions = '';
}
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
this.grid = grid;
this.addIdsToGridTableRows();
grid
.getContainer()
.find(GridMap.gridTable)
.tableDnD({
dragHandle: GridMap.dragHandler,
onDragClass: 'dragging-row',
onDragStart: () => {
this.originalPositions = decodeURIComponent($.tableDnD.serialize());
},
onDrop: (table: HTMLElement, row: HTMLElement) => this.handleCategoryPositionChange(row),
});
}
/**
* When position is changed handle update
*
* @param {HTMLElement} row
*
* @private
*/
handleCategoryPositionChange(row: HTMLElement): void {
const positions = decodeURIComponent($.tableDnD.serialize());
const way = this.originalPositions.indexOf(row.id) < positions.indexOf(row.id)
? 1
: 0;
const $categoryPositionContainer = $(row).find(
GridMap.position(this.grid.getId()),
);
const categoryId = $categoryPositionContainer.data('id');
const categoryParentId = $categoryPositionContainer.data('id-parent');
const positionUpdateUrl = $categoryPositionContainer.data(
'position-update-url',
);
let params = positions.replace(
new RegExp(GridMap.specificGridTable(this.grid.getId()), 'g'),
'positions',
);
const queryParams = {
id_category_parent: categoryParentId,
id_category_to_move: categoryId,
way,
found_first: 0,
};
if (positions.indexOf('_0&') !== -1) {
queryParams.found_first = 1;
}
params += `&${$.param(queryParams)}`;
this.updateCategoryPosition(positionUpdateUrl, params);
}
/**
* Add ID's to Grid table rows to make tableDnD.onDrop() function work.
*
* @private
*/
addIdsToGridTableRows(): void {
this.grid
.getContainer()
.find(GridMap.gridTable)
.find(GridMap.gridPosition(this.grid.getId()))
.each((index, positionWrapper) => {
const $positionWrapper = $(positionWrapper);
const categoryId = $positionWrapper.data('id');
const categoryParentId = $positionWrapper.data('id-parent');
const position = $positionWrapper.data('position');
const id = `tr_${categoryParentId}_${categoryId}_${position}`;
$positionWrapper.closest('tr').attr('id', id);
});
}
/**
* Update categories listing with new positions
*
* @private
*/
updateCategoryIdsAndPositions(): void {
this.grid
.getContainer()
.find(GridMap.gridTable)
.find(GridMap.gridPosition(this.grid.getId()))
.each((index, positionWrapper) => {
const $positionWrapper = $(positionWrapper);
const $row = $positionWrapper.closest('tr');
const offset = $positionWrapper.data('pagination-offset');
const newPosition = offset > 0 ? index + offset : index;
const oldId = $row.attr('id');
if (oldId) {
$row.attr('id', oldId.replace(/_[0-9]$/g, `_${newPosition}`));
}
$positionWrapper.find(GridMap.selectPosition).text(newPosition + 1);
$positionWrapper.data('position', newPosition);
});
}
/**
* Process categories positions update
*
* @param {String} url
* @param {String} params
*
* @private
*/
updateCategoryPosition(url: string, params: string): void {
$.post({
url,
headers: {
'cache-control': 'no-cache',
},
data: params,
dataType: 'json',
}).then((response) => {
if (response.success) {
window.showSuccessMessage(response.message);
} else {
window.showErrorMessage(response.message);
}
this.updateCategoryIdsAndPositions();
});
}
}

View File

@@ -0,0 +1,98 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class AsyncToggleColumnExtension submits toggle action using AJAX
*/
export default class AsyncToggleColumnExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.find(GridMap.gridTable)
.on('click', GridMap.togglableRow, (event) => {
const $button = $(event.currentTarget);
if (!$button.hasClass('ps-switch')) {
event.preventDefault();
}
const $newStateInput = $button.find('input:checked');
const newState = Boolean($newStateInput.val());
$.post({
url: $button.data('toggle-url'),
})
.then((response) => {
if (response.status) {
window.showSuccessMessage(response.message);
this.toggleButtonDisplay($button);
return;
}
this.showErrorMessage(response.message, $newStateInput.prop('name'), !newState);
})
.catch((error: AjaxError) => {
const response = error.responseJSON;
this.showErrorMessage(response.message, $newStateInput.prop('name'), !newState);
});
});
}
private showErrorMessage(message: string, switchName: string, initialState: boolean): void {
// We need to toggle back the switch state
this.toggleSwitch(switchName, initialState);
window.showErrorMessage(message);
}
private toggleSwitch(switchName: string, checked: boolean): void {
const $switchOn = $(`[name="${switchName}"][value="1"]`);
const $switchOff = $(`[name="${switchName}"][value="0"]`);
if ($switchOn.is(':checked') !== checked) {
$switchOn.prop('checked', checked);
}
if ($switchOff.is(':checked') === checked) {
$switchOff.prop('checked', !checked);
}
}
/**
* Toggle button display from enabled to disabled and other way around
*
* @param {jQuery} $button
*
* @private
*/
private toggleButtonDisplay($button: JQuery): void {
const isActive = $button.hasClass('grid-toggler-icon-valid');
const classToAdd = isActive
? 'grid-toggler-icon-not-valid'
: 'grid-toggler-icon-valid';
const classToRemove = isActive
? 'grid-toggler-icon-valid'
: 'grid-toggler-icon-not-valid';
const icon = isActive ? 'clear' : 'check';
$button.removeClass(classToRemove);
$button.addClass(classToAdd);
if ($button.hasClass('material-icons')) {
$button.text(icon);
}
}
}

View File

@@ -0,0 +1,108 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class ExportToSqlManagerExtension extends grid with exporting query to SQL Manager
*/
export default class ExportToSqlManagerExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getHeaderContainer()
.on('click', GridMap.actions.showQuery, () => this.onShowSqlQueryClick(grid));
grid
.getHeaderContainer()
.on('click', GridMap.actions.exportQuery, () => this.onExportSqlManagerClick(grid));
}
/**
* Invoked when clicking on the "show sql query" toolbar button
*
* @param {Grid} grid
*
* @private
*/
onShowSqlQueryClick(grid: Grid): void {
const $sqlManagerForm = $(GridMap.actions.showModalForm(grid.getId()));
this.fillExportForm($sqlManagerForm, grid);
const $modal = $(GridMap.actions.showModalGrid(grid.getId()));
$modal.modal('show');
$modal.on('click', GridMap.sqlSubmit, () => $sqlManagerForm.submit());
}
/**
* Invoked when clicking on the "export to the sql query" toolbar button
*
* @param {Grid} grid
*
* @private
*/
private onExportSqlManagerClick(grid: Grid): void {
const $sqlManagerForm = $(GridMap.actions.showModalForm(grid.getId()));
this.fillExportForm($sqlManagerForm, grid);
$sqlManagerForm.submit();
}
/**
* Fill export form with SQL and it's name
*
* @param {jQuery} $sqlManagerForm
* @param {Grid} grid
*
* @private
*/
private fillExportForm($sqlManagerForm: JQuery, grid: Grid) {
const query = grid
.getContainer()
.find(GridMap.gridTable)
.data('query');
$sqlManagerForm.find('textarea[name="sql"]').val(query);
$sqlManagerForm
.find('input[name="name"]')
.val(this.getNameFromBreadcrumb());
}
/**
* Get export name from page's breadcrumb
*
* @return {String}
*
* @private
*/
private getNameFromBreadcrumb(): string {
const $breadcrumbs = $(GridMap.headerToolbar).find(GridMap.breadcrumbItem);
let name = '';
$breadcrumbs.each((i, item) => {
const $breadcrumb = $(item);
const breadcrumbTitle = $breadcrumb.find('a').length > 0
? $breadcrumb.find('a').text()
: $breadcrumb.text();
if (name.length > 0) {
name = name.concat(' > ');
}
name = name.concat(breadcrumbTitle);
});
return name;
}
}

View File

@@ -0,0 +1,29 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import resetSearch from '@app/utils/reset_search';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class FiltersResetExtension extends grid with filters resetting
*/
export default class FiltersResetExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid.getContainer().on('click', GridMap.resetSearch, (event) => {
resetSearch(
$(event.currentTarget).data('url'),
$(event.currentTarget).data('redirect'),
);
});
}
}

View File

@@ -0,0 +1,27 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
/**
* Responsible for grid filters search and reset button availability when filter inputs changes.
*/
export default class FiltersSubmitButtonEnablerExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
const $filtersRow = grid.getContainer().find(GridMap.columnFilters);
$filtersRow.find(GridMap.gridSearchButton).prop('disabled', true);
$filtersRow.find(GridMap.inputAndSelect).on('input dp.change', () => {
$filtersRow.find(GridMap.gridSearchButton).prop('disabled', false);
$filtersRow.find(GridMap.gridResetButton).prop('hidden', false);
});
}
}

View File

@@ -0,0 +1,100 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
import {isUndefined} from '@components/typeguard';
const {$} = window;
type OnClickCallbackFunction = (button: HTMLElement) => void;
/**
* Class LinkRowActionExtension handles link row actions
*/
export default class LinkRowActionExtension {
private readonly onClick?: OnClickCallbackFunction | undefined;
constructor(onClick:OnClickCallbackFunction | undefined = undefined) {
this.onClick = onClick;
}
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
this.initRowLinks(grid);
this.initConfirmableActions(grid);
}
/**
* Extend grid
*
* @param {Grid} grid
*/
initConfirmableActions(grid: Grid): void {
grid.getContainer().on('click', GridMap.rows.linkRowAction, (event) => {
const confirmMessage = $(event.currentTarget).data('confirm-message');
if (confirmMessage.length && !window.confirm(confirmMessage)) {
event.preventDefault();
}
});
}
/**
* Add a click event on rows that matches the first link action (if present)
*
* @param {Grid} grid
*/
initRowLinks(grid: Grid): void {
const onClickCallback = this.onClick;
$('tr', grid.getContainer()).each(function initEachRow() {
const $parentRow = $(this);
$(GridMap.rows.linkRowActionClickableFirst, $parentRow).each(
function propagateFirstLinkAction() {
const $rowAction = $(this);
const $parentCell = $rowAction.closest('td');
const clickableCells = $(GridMap.rows.clickableTd, $parentRow).not(
$parentCell,
);
let isDragging = false;
clickableCells.addClass('cursor-pointer').on('mousedown', () => {
$(window).on('mousemove', () => {
isDragging = true;
$(window).off('mousemove');
});
});
clickableCells.on('mouseup', () => {
const wasDragging = isDragging;
isDragging = false;
$(window).off('mousemove');
if (!wasDragging) {
const confirmMessage = $rowAction.data('confirm-message');
if (
!confirmMessage.length
|| (window.confirm(confirmMessage) && $rowAction.attr('href'))
) {
if (!isUndefined(onClickCallback) && !isUndefined($rowAction.get(0))) {
onClickCallback($rowAction.get(0) as HTMLElement);
} else {
document.location.href = <string>$rowAction.attr('href');
}
}
}
});
},
);
});
}
}

View File

@@ -0,0 +1,66 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Allows submitting form inside modals.
* Form must be inside modal, see example structure below:
*
* <div class="modal" id="uniqueModalId">
* <form data-bulk-inputs-id="bulkInputs">
* <div class="d-none">
* <div id="bulkInputs" data-prototype="<input type="hidden" name="__name__"/>"></div>
* </div>
* </form>
* </div>
*
* Note that "data-prototype" is required to add checked items to the form. "__name__"
* will be replaced with value of bulk checkbox.
*/
export default class ModalFormSubmitExtension {
extend(grid: Grid): void {
grid
.getContainer()
.on(
'click',
GridMap.bulks.modalFormSubmitBtn,
(event: JQueryEventObject) => {
const modalId = $(event.target).data('modal-id');
const $modal = $(`#${modalId}`);
$modal.modal('show');
$modal.find(GridMap.actions.submitModalFormBtn).on('click', () => {
const $form = $modal.find('form');
const $bulkInputsBlock = $form.find(
GridMap.actions.bulkInputsBlock($form.data('bulk-inputs-id')),
);
const $checkboxes = grid
.getContainer()
.find(GridMap.bulks.checkedCheckbox);
$checkboxes.each((i, element) => {
const $checkbox = $(element);
const input = $bulkInputsBlock
.data('prototype')
.replace(/__name__/g, $checkbox.val());
const $input = $($.parseHTML(input)[0]);
$input.val(<string>$checkbox.val());
$form.append($input);
});
$form.submit();
});
},
);
}
}

View File

@@ -0,0 +1,307 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
import {isUndefined} from '@components/typeguard';
import 'tablednd/dist/jquery.tablednd.min';
const {$} = window;
interface RowDatas {
rowMarker: string;
offset: number;
}
interface DNDPositions {
rowId: string;
oldPosition: number;
newPosition: number;
}
/**
* Class PositionExtension extends Grid with reorderable positions
*/
export default class PositionExtension {
grid: Grid;
constructor(grid: Grid) {
this.grid = grid;
}
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
this.grid = grid;
this.addIdsToGridTableRows();
grid
.getContainer()
.find(GridMap.gridTable)
.tableDnD({
onDragClass: GridMap.onDragClass,
dragHandle: GridMap.dragHandler,
onDrop: (table: HTMLElement, row: HTMLElement) => this.handlePositionChange(row),
});
grid
.getContainer()
.find('.js-drag-handle')
.on(
'mouseenter',
function () {
$(this)
.closest('tr')
.addClass('hover');
},
).on(
'mouseleave',
function () {
$(this)
.closest('tr')
.removeClass('hover');
},
);
this.setReorderButtonLabel();
this.getReorderButton().on('click', (event) => this.oncClickReorderButton(event));
}
/**
* When position is changed handle update
*
* @param {HTMLElement} row
*
* @private
*/
private handlePositionChange(row: HTMLElement): void {
const $rowPositionContainer = $(row).find(
GridMap.gridPositionFirst(this.grid.getId()),
);
const updateUrl = $rowPositionContainer.data('update-url');
const method = $rowPositionContainer.data('update-method');
const positions = this.getRowsPositions();
const params = {positions};
this.updatePosition(updateUrl, params, method);
}
/**
* Returns the current table positions
* @returns {Array}
* @private
*/
private getRowsPositions(): Array<DNDPositions> {
const tableData = JSON.parse($.tableDnD.jsonize());
const rowsData = tableData[`${this.grid.getId()}_grid_table`];
const completeRowsData = [];
let trData;
// retrieve dragAndDropOffset offset to have all needed data
// for positions mapping evolution over time
for (let i = 0; i < rowsData.length; i += 1) {
trData = this.grid.getContainer().find(`#${rowsData[i]}`);
completeRowsData.push({
rowMarker: rowsData[i],
offset: trData.data('dragAndDropOffset'),
});
}
return this.computeMappingBetweenOldAndNewPositions(completeRowsData);
}
/**
* Add ID's to Grid table rows to make tableDnD.onDrop() function work.
*
* @private
*/
private addIdsToGridTableRows(): void {
let counter = 0;
this.grid
.getContainer()
.find(GridMap.gridTablePosition(this.grid.getId()))
.each((index, positionWrapper) => {
const $positionWrapper = $(positionWrapper);
const rowId = $positionWrapper.data('id');
const position = $positionWrapper.data('position');
const id = `row_${rowId}_${position}`;
$positionWrapper.closest('tr').attr('id', id);
$positionWrapper.closest('td').addClass(GridMap.dragHandlerClass);
$positionWrapper.closest('tr').data('dragAndDropOffset', counter);
counter += 1;
});
}
/**
* Process rows positions update
*
* @param {String} url
* @param {Object} params
* @param {String} method
*
* @private
*/
private updatePosition(
url: string,
params: Record<string, Array<DNDPositions>>,
method: string,
): void {
const isGetOrPostMethod = ['GET', 'POST'].includes(method);
const $form = $('<form>', {
action: url,
method: isGetOrPostMethod ? method : 'POST',
}).appendTo('body');
const positionsNb = params.positions.length;
let position;
for (let i = 0; i < positionsNb; i += 1) {
position = params.positions[i];
$form.append(
$('<input>', {
type: 'hidden',
name: `positions[${i}][rowId]`,
value: position.rowId,
}),
$('<input>', {
type: 'hidden',
name: `positions[${i}][oldPosition]`,
value: position.oldPosition,
}),
$('<input>', {
type: 'hidden',
name: `positions[${i}][newPosition]`,
value: position.newPosition,
}),
);
}
// This _method param is used by Symfony to simulate DELETE and PUT methods
if (!isGetOrPostMethod) {
$form.append(
$('<input>', {
type: 'hidden',
name: '_method',
value: method,
}),
);
}
$form.submit();
}
/**
* Rows have been reordered. This function
* finds, for each row ID: the old position, the new position
*
* @returns {Array}
* @private
*/
private computeMappingBetweenOldAndNewPositions(
rowsData: Array<RowDatas>,
): Array<DNDPositions> {
const regex = /^row_(?<rowId>\d+)_(?<oldPosition>\d+)$/;
const mapping: Array<DNDPositions> = [];
// First loop is to create the mapping objects with old positions
for (let i = 0; i < rowsData.length; i += 1) {
const regexResult = regex.exec(rowsData[i].rowMarker);
if (regexResult
&& !isUndefined(regexResult.groups)
&& !isUndefined(regexResult.groups.rowId)
&& !isUndefined(regexResult.groups.oldPosition)) {
const oldPosition: number = parseInt(regexResult?.groups?.oldPosition, 10);
mapping[i] = {
rowId: regexResult.groups.rowId,
oldPosition,
newPosition: oldPosition,
};
}
// Second loop, now that all positions are defined for all rows we can switch the position when needed
for (let j = 0; j < rowsData.length; j += 1) {
if (!isUndefined(rowsData[j])
&& !isUndefined(rowsData[j].offset)
&& !isUndefined(mapping[rowsData[j].offset])
&& !isUndefined(mapping[j])) {
// This row will have as a new position the old position of the current one
mapping[rowsData[j].offset].newPosition = mapping[j].oldPosition;
}
}
}
return mapping;
}
/**
* Check if position reorder is active
*
* @private
*/
private isPositionsReorderActive(): boolean {
return this.grid.getContainer()
.find('.ps-sortable-column[data-sort-col-name="position"]')
.first()
.data('sort-is-current');
}
/**
* Get reorder button
*
* @private
*/
private getReorderButton(): JQuery<HTMLElement> {
return this.grid
.getContainer()
.find('.js-btn-reorder-positions')
.first();
}
/**
* Set reorder button label in function of sortable column state.
*
* @private
*/
private setReorderButtonLabel(): void {
const rearrangeButton = this.getReorderButton();
if (this.isPositionsReorderActive()) {
rearrangeButton.hide();
} else {
rearrangeButton.data('label-reorder');
}
}
/**
* Onclick reorder button
*
* @param event
* @private
*/
private oncClickReorderButton(event: JQuery.Event): void {
event.preventDefault();
// If positions are actually being reordered...
if (this.isPositionsReorderActive()) {
// we need to reset filters and order by of the grid
this.grid.getContainer()
.find('.ps-sortable-column')
.first()
.click();
} else {
// Else, we need to set the position column as the current sort ordering
this.grid.getContainer()
.find('.ps-sortable-column[data-sort-col-name="position"]')
.first()
.click();
}
}
}

View File

@@ -0,0 +1,230 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Extends grid with preview functionality.
*/
export default class PreviewExtension {
locks: Array<unknown>;
expandSelector: string;
previewOpenClass: string;
collapseSelector: string;
previewToggleSelector: string;
previewCustomization: (previewTemplate: JQuery) => void;
$gridContainer: JQuery;
constructor(previewCustomization: (previewTemplate: JQuery) => void, grid: Grid) {
this.locks = [];
this.expandSelector = GridMap.expand;
this.collapseSelector = GridMap.collapse;
this.previewOpenClass = 'preview-open';
this.previewToggleSelector = GridMap.previewToggle;
this.previewCustomization = previewCustomization;
this.$gridContainer = $(grid.getContainer);
}
/**
* Extends provided grid with preview functionality
*
* @param grid
*/
extend(grid: Grid): void {
this.$gridContainer = $(grid.getContainer);
this.$gridContainer.find('tbody tr').on('mouseover mouseleave', (event: JQueryEventObject) => this.handleIconHovering(event));
this.$gridContainer.find(this.previewToggleSelector).on('click', (event: JQueryEventObject) => this.togglePreview(event));
}
/**
* Shows/hides preview toggling icons
*
* @param event
* @private
*/
private handleIconHovering(event: JQueryEventObject) {
const $previewToggle = $(event.currentTarget).find(this.previewToggleSelector);
if (event.type === 'mouseover' && !$(event.currentTarget).hasClass(this.previewOpenClass)) {
this.showExpandIcon($previewToggle);
} else {
this.hideExpandIcon($previewToggle);
}
}
/**
* Shows/hides preview
*
* @param event
* @private
*/
togglePreview(event: JQueryEventObject): void {
const $previewToggle = $(event.currentTarget);
const $columnRow = $previewToggle.closest('tr');
if ($columnRow.hasClass(this.previewOpenClass)) {
$columnRow.next(GridMap.previewRow).remove();
$columnRow.removeClass(this.previewOpenClass);
this.showExpandIcon($columnRow);
this.hideCollapseIcon($columnRow);
return;
}
this.closeOpenedPreviews();
const dataUrl = $(event.currentTarget).data('preview-data-url');
if (this.isLocked(dataUrl)) {
return;
}
// Prevents loading preview multiple times.
// Uses "dataUrl" as lock key.
this.lock(dataUrl);
$.ajax({
url: dataUrl,
method: 'GET',
dataType: 'json',
complete: () => {
this.unlock(dataUrl);
},
})
.then((response) => {
this.renderPreviewContent($columnRow, response.preview);
})
.catch((e: AjaxError) => {
window.showErrorMessage(e.responseJSON.message);
});
}
/**
* Renders preview content
*
* @param $columnRow
* @param content
*
* @private
*/
private renderPreviewContent($columnRow: JQuery<Element>, content: string) {
const rowColumnCount = $columnRow.find('td').length;
const $previewTemplate = $(`
<tr class="preview-row">
<td colspan="${rowColumnCount}">${content}</td>
</tr>
`);
$columnRow.addClass(this.previewOpenClass);
this.showCollapseIcon($columnRow);
this.hideExpandIcon($columnRow);
if (typeof this.previewCustomization === 'function') {
this.previewCustomization($previewTemplate);
}
$columnRow.after($previewTemplate);
}
/**
* Shows preview expanding icon
*
* @param parent
* @private
*/
private showExpandIcon(parent: JQuery<Element>): void {
parent.find(this.expandSelector).removeClass('d-none');
}
/**
* Hides preview expanding icon
*
* @param parent
* @private
*/
private hideExpandIcon(parent: JQuery<Element>): void {
parent.find(this.expandSelector).addClass('d-none');
}
/**
* Shows preview collapsing icon
*
* @param parent
* @private
*/
private showCollapseIcon(parent: JQuery<Element>): void {
parent.find(this.collapseSelector).removeClass('d-none');
}
/**
* Hides preview collapsing icon
*
* @param parent
* @private
*/
private hideCollapseIcon(parent: JQuery<Element>): void {
parent.find(this.collapseSelector).addClass('d-none');
}
isLocked(key: number): boolean {
return this.locks.indexOf(key) !== -1;
}
lock(key: number): void {
if (this.isLocked(key)) {
return;
}
this.locks.push(key);
}
unlock(key: number): void {
const index = this.locks.indexOf(key);
if (index === -1) {
return;
}
this.locks.splice(index, 1);
}
/**
* Close all previews that are open.
*
* @private
*/
private closeOpenedPreviews(): void {
const $rows = this.$gridContainer.find(GridMap.gridTbody).find(GridMap.trNotPreviewRow);
$.each($rows, (i, row) => {
const $row = $(row);
if (!$row.hasClass(this.previewOpenClass)) {
return;
}
const $previewRow = $row.next();
if (!$previewRow.hasClass('preview-row')) {
return;
}
$previewRow.remove();
$row.removeClass(this.previewOpenClass);
this.hideCollapseIcon($row);
});
}
}

View File

@@ -0,0 +1,25 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
/**
* Class ReloadListExtension extends grid with "List reload" action
*/
export default class ReloadListExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getHeaderContainer()
.on('click', GridMap.commonRefreshListAction, () => {
window.location.reload();
});
}
}

View File

@@ -0,0 +1,24 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import TableSorting from '@app/utils/table-sorting';
import GridMap from '@components/grid/grid-map';
/**
* Class ReloadListExtension extends grid with "List reload" action
*/
export default class SortingExtension {
/**
* Extend grid
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
const $sortableTable = grid.getContainer().find(GridMap.table);
new TableSorting($sortableTable).attach();
}
}

View File

@@ -0,0 +1,95 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import ConfirmModal from '@components/modal';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Handles submit of grid actions
*/
export default class SubmitBulkActionExtension {
/**
* Extend grid with bulk action submitting
*
* @param {Grid} grid
*/
extend(grid: Grid): void {
grid
.getContainer()
.on('click', GridMap.bulks.submitAction, (event: JQueryEventObject) => {
this.submit(event, grid);
});
}
/**
* Handle bulk action submitting
*
* @param {Event} event
* @param {Grid} grid
*
* @private
*/
private submit(event: JQueryEventObject, grid: Grid): void {
const $submitBtn = $(event.currentTarget);
const confirmMessage = $submitBtn.data('confirm-message');
const confirmTitle = $submitBtn.data('confirmTitle');
if (confirmMessage !== undefined && confirmMessage.length > 0) {
if (confirmTitle !== undefined) {
this.showConfirmModal($submitBtn, grid, confirmMessage, confirmTitle);
} else if (window.confirm(confirmMessage)) {
this.postForm($submitBtn, grid);
}
} else {
this.postForm($submitBtn, grid);
}
}
/**
* @param {jQuery} $submitBtn
* @param {Grid} grid
* @param {string} confirmMessage
* @param {string} confirmTitle
*/
private showConfirmModal(
$submitBtn: JQuery<Element>,
grid: Grid,
confirmMessage: string,
confirmTitle: string,
): void {
const confirmButtonLabel = $submitBtn.data('confirmButtonLabel');
const closeButtonLabel = $submitBtn.data('closeButtonLabel');
const confirmButtonClass = $submitBtn.data('confirmButtonClass');
const modal = new ConfirmModal(
{
id: GridMap.confirmModal(grid.getId()),
confirmTitle,
confirmMessage,
confirmButtonLabel,
closeButtonLabel,
confirmButtonClass,
},
() => this.postForm($submitBtn, grid),
);
modal.show();
}
/**
* @param {jQuery} $submitBtn
* @param {Grid} grid
*/
private postForm($submitBtn: JQuery<Element>, grid: Grid): void {
const $form = $(GridMap.filterForm(grid.getId()));
$form.attr('action', $submitBtn.data('form-url'));
$form.attr('method', $submitBtn.data('form-method'));
$form.submit();
}
}

View File

@@ -0,0 +1,56 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {Grid} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$} = window;
/**
* Class SubmitGridActionExtension handles grid action submits
*/
export default class SubmitGridActionExtension {
extend(grid: Grid): void {
grid
.getHeaderContainer()
.on(
'click',
GridMap.bulks.gridSubmitAction,
(event: JQueryEventObject) => {
this.handleSubmit(event, grid);
},
);
}
/**
* Handle grid action submit.
* It uses grid form to submit actions.
*
* @param {Event} event
* @param {Grid} grid
*
* @private
*/
private handleSubmit(event: JQueryEventObject, grid: Grid): void {
const $submitBtn = $(event.currentTarget);
const confirmMessage = $submitBtn.data('confirm-message');
if (
typeof confirmMessage !== 'undefined'
&& confirmMessage.length > 0
&& !window.confirm(confirmMessage)
) {
return;
}
const $form = $(GridMap.filterForm(grid.getId()));
$form.attr('action', $submitBtn.data('url'));
$form.attr('method', $submitBtn.data('method'));
$form
.find(GridMap.actions.tokenInput(grid.getId()))
.val($submitBtn.data('csrf'));
$form.submit();
}
}

View File

@@ -0,0 +1,84 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
export default {
bulks: {
deleteCategories: '.js-delete-categories-bulk-action',
deleteCategoriesModal: (id: string): string => `#${id}_grid_delete_categories_modal`,
checkedCheckbox: '.js-bulk-action-checkbox:checked',
deleteCustomers: '.js-delete-customers-bulk-action',
deleteCustomerModal: (id: string): string => `#${id}_grid_delete_customers_modal`,
submitDeleteCategories: '.js-submit-delete-categories',
submitDeleteCustomers: '.js-submit-delete-customers',
categoriesToDelete: '#delete_categories_categories_to_delete',
customersToDelete: '#delete_customers_customers_to_delete',
actionSelectAll: '.js-bulk-action-select-all',
bulkActionCheckbox: '.js-bulk-action-checkbox',
bulkActionBtn: '.js-bulk-actions-btn',
openTabsBtn: '.js-bulk-action-btn.open_tabs',
tableChoiceOptions: 'table.table .js-choice-options',
choiceOptions: '.js-choice-options',
modalFormSubmitBtn: '.js-bulk-modal-form-submit-btn',
submitAction: '.js-bulk-action-submit-btn',
ajaxAction: '.js-bulk-action-ajax-btn',
gridSubmitAction: '.js-grid-action-submit-btn',
},
rows: {
categoryDeleteAction: '.js-delete-category-row-action',
customerDeleteAction: '.js-delete-customer-row-action',
linkRowAction: '.js-link-row-action',
linkRowActionClickableFirst:
'.js-link-row-action[data-clickable-row=1]:first',
clickableTd: 'td.clickable',
imageTypeDeleteAction: '.js-delete-image-type-row-action',
deleteImageTypeModal: (id: string): string => `#${id}_grid_delete_image_type_modal`,
submitDeleteImageType: '.js-submit-delete-image-type',
},
actions: {
showQuery: '.js-common_show_query-grid-action',
exportQuery: '.js-common_export_sql_manager-grid-action',
showModalForm: (id: string): string => `#${id}_common_show_query_modal_form`,
showModalGrid: (id: string): string => `#${id}_grid_common_show_query_modal`,
modalFormSubmitBtn: '.js-bulk-modal-form-submit-btn',
submitModalFormBtn: '.js-submit-modal-form-btn',
bulkInputsBlock: (id: string): string => `#${id}`,
tokenInput: (id: string): string => `input[name="${id}[_token]"]`,
ajaxBulkActionConfirmModal: (id: string, bulkAction: string): string => `${id}-ajax-${bulkAction}-confirm-modal`,
ajaxBulkActionProgressModal: (id: string, bulkAction: string): string => `${id}-ajax-${bulkAction}-progress-modal`,
},
position: (id: string): string => `.js-${id}-position:first`,
confirmModal: (id: string): string => `${id}-grid-confirm-modal`,
gridTable: '.js-grid-table',
dragHandler: '.js-drag-handle',
dragHandlerClass: 'js-drag-handle',
specificGridTable: (id: string): string => `${id}_grid_table`,
grid: (id: string): string => `#${id}_grid`,
gridPanel: '.js-grid-panel',
gridHeader: '.js-grid-header',
gridPosition: (id: string): string => `.js-${id}-position`,
gridTablePosition: (id: string): string => `.js-grid-table .js-${id}-position`,
gridPositionFirst: (id: string): string => `.js-${id}-position:first`,
selectPosition: 'js-position',
togglableRow: '.ps-togglable-row',
dropdownItem: '.js-dropdown-item',
table: 'table.table',
headerToolbar: '.header-toolbar',
breadcrumbItem: '.breadcrumb-item',
resetSearch: '.js-reset-search',
expand: '.js-expand',
collapse: '.js-collapse',
columnFilters: '.column-filters',
gridSearchButton: '.grid-search-button',
gridResetButton: '.grid-reset-button',
inputAndSelect: 'input:not(.js-bulk-action-select-all), select',
previewToggle: '.preview-toggle',
previewRow: '.preview-row',
gridTbody: '.grid-table tbody',
trNotPreviewRow: 'tr:not(.preview-row)',
commonRefreshListAction: '.js-common_refresh_list-grid-action',
filterForm: (id: string): string => `#${id}_filter_form`,
onDragClass: 'position-row-while-drag',
sqlSubmit: '.btn-sql-submit',
};

View File

@@ -0,0 +1,64 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
import {GridExtension} from '@PSTypes/grid';
import GridMap from '@components/grid/grid-map';
const {$}: Window = window;
/**
* Class is responsible for handling Grid events
*/
export default class Grid {
id: string;
$container: JQuery;
/**
* Grid id
*
* @param {string} id
*/
constructor(id: string) {
this.id = id;
this.$container = $(GridMap.grid(this.id));
}
/**
* Get grid id
*
* @returns {string}
*/
getId(): string {
return this.id;
}
/**
* Get grid container
*
* @returns {jQuery}
*/
getContainer(): JQuery {
return this.$container;
}
/**
* Get grid header container
*
* @returns {jQuery}
*/
getHeaderContainer(): JQuery {
return this.$container.closest(GridMap.gridPanel).find(GridMap.gridHeader);
}
/**
* Extend grid with external extensions
*
* @param {object} extension
*/
addExtension(extension: GridExtension): void {
extension.extend(this);
}
}