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,97 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
// eslint-disable-next-line
function PerformancePage(addServerUrl, removeServerUrl, testServerUrl) {
this.addServerUrl = addServerUrl;
this.removeServerUrl = removeServerUrl;
this.testServerUrl = testServerUrl;
this.getAddServerUrl = function () {
return this.addServerUrl;
};
this.getRemoveServerlUrl = function () {
return this.removeServerUrl;
};
this.getTestServerUrl = function () {
return this.testServerUrl;
};
this.getFormValues = function () {
return {
server_ip: document.getElementById('memcache_ip').value,
server_port: document.getElementById('memcache_port').value,
server_weight: document.getElementById('memcache_weight').value,
};
};
this.createRow = function (params) {
const serversTable = document.getElementById('servers-table');
const newRow = document.createElement('tr');
newRow.setAttribute('id', `row_${params.id}`);
newRow.innerHTML = `<td>${params.id}</td>\n`
+ `<td>${params.server_ip}</td>\n`
+ `<td>${params.server_port}</td>\n`
+ `<td>${params.server_weight}</td>\n`
+ '<td>\n'
// eslint-disable-next-line
+ ` <a class="btn btn-default" href="#" onclick="app.removeServer(${params.id});"><i class="material-icons">remove_circle</i> Remove</a>\n`
+ '</td>\n';
serversTable.appendChild(newRow);
};
this.addServer = function () {
const app = this;
this.send(this.getAddServerUrl(), 'POST', this.getFormValues(), (results) => {
// eslint-disable-next-line
if (!results.hasOwnProperty('error')) {
app.createRow(results);
}
});
};
this.removeServer = function (serverId, removeMsg) {
const removeOk = confirm(removeMsg);
if (removeOk) {
this.send(this.getRemoveServerlUrl(), 'DELETE', {server_id: serverId}, (results) => {
if (results === undefined) {
const row = document.getElementById(`row_${serverId}`);
row.parentNode.removeChild(row);
}
});
}
};
this.testServer = function () {
const app = this;
this.send(this.getTestServerUrl(), 'GET', this.getFormValues(), (results) => {
// eslint-disable-next-line
if (results.hasOwnProperty('error') || results.test === false) {
app.addClass('is-invalid');
return;
}
app.addClass('is-valid');
});
};
this.addClass = function (className) {
const serverFormInputs = document.querySelectorAll('#server-form input[type=text]');
for (let i = 0; i < serverFormInputs.length; i += 1) {
serverFormInputs[i].className = `form-control ${className}`;
}
};
this.send = function (url, method, params, callback) {
return $.ajax({
url,
method,
data: params,
}).done(callback);
};
}

View File

@@ -0,0 +1,90 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
const PerformancePageUI = {
displaySmartyCache() {
const CACHE_ENABLED = '1';
const smartyCacheSelected = document.querySelector('input[name="smarty[cache]"]:checked');
document.querySelectorAll('.smarty-cache-option').forEach((element) => {
element.classList.toggle('d-none', smartyCacheSelected.value !== CACHE_ENABLED);
});
},
displayDebugModeOptions() {
const DEBUG_MODE_ON = '1';
const debugModeOn = document.querySelector('input[name="debug_mode[debug_mode]"]:checked');
document.querySelectorAll('.debug-mode-option').forEach((element) => {
element.classList.toggle('d-none', debugModeOn.value !== DEBUG_MODE_ON);
});
},
displayCacheSystems() {
const CACHE_ENABLED = '1';
const cacheEnabledInput = document.querySelector('input[name="caching[use_cache]"]:checked');
const cachingElements = document.getElementsByClassName('memcache');
if (cacheEnabledInput.value === CACHE_ENABLED) {
for (let i = 0; i < cachingElements.length; i += 1) {
cachingElements[i].style.display = '';
}
return;
}
for (let i = 0; i < cachingElements.length; i += 1) {
cachingElements[i].style.display = 'none';
}
},
displayMemcacheServers() {
const CACHE_ENABLED = '1';
const cacheEnabledInput = document.querySelector('input[name="caching[use_cache]"]:checked');
const cacheSelected = document.querySelector('input[name="caching[caching_system]"]:checked');
const memcacheServersListBlock = document.getElementById('servers-list');
const newServerBtn = document.getElementById('new-server-btn');
const isMemcache = cacheSelected
&& (cacheSelected.value === 'CacheMemcache' || cacheSelected.value === 'CacheMemcached');
if (isMemcache && cacheEnabledInput.value === CACHE_ENABLED) {
memcacheServersListBlock.style.display = 'block';
newServerBtn.style.display = 'block';
return;
}
memcacheServersListBlock.style.display = 'none';
newServerBtn.style.display = 'none';
},
};
/**
* Animations on form values.
*/
window.addEventListener('load', () => {
PerformancePageUI.displaySmartyCache();
PerformancePageUI.displayDebugModeOptions();
PerformancePageUI.displayCacheSystems();
PerformancePageUI.displayMemcacheServers();
});
const cacheSystemInputs = document.querySelectorAll('input[type=radio]');
let {length} = cacheSystemInputs;
// eslint-disable-next-line
while (length--) {
// eslint-disable-next-line
cacheSystemInputs[length].addEventListener('change', (e) => {
const name = e.target.getAttribute('name');
if (name === 'caching[use_cache]') {
return PerformancePageUI.displayCacheSystems();
}
if (name === 'smarty[cache]') {
return PerformancePageUI.displaySmartyCache();
}
if (name === 'debug_mode[debug_mode]') {
return PerformancePageUI.displayDebugModeOptions();
}
if (name === 'caching[caching_system]') {
return PerformancePageUI.displayMemcacheServers();
}
});
}

View File

@@ -0,0 +1,73 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
(function ($) {
$.fn.categorytree = function (settings) {
const isMethodCall = (typeof settings === 'string'); // is this a method call like $().categorytree("unselect")
const returnValue = this;
// if a method call execute the method on all selected instances
if (isMethodCall) {
switch (settings) {
case 'unselect':
this.find('.radio > label > input:radio').prop('checked', false);
// TODO: add a callback method feature?
break;
case 'unfold':
this.find('ul').show();
this.find('li').has('ul').removeClass('more').addClass('less');
break;
case 'fold':
this.find('ul ul').hide();
this.find('li').has('ul').removeClass('less').addClass('more');
break;
default:
// eslint-disable-next-line
throw 'Unknown method';
}
// eslint-disable-next-line
}
// initialize tree
else {
const clickHandler = function (event) {
let $ui = $(event.target);
if ($ui.attr('type') === 'radio' || $ui.attr('type') === 'checkbox') {
return;
}
event.stopPropagation();
if ($ui.next('ul').length === 0) {
$ui = $ui.parent();
}
$ui.next('ul').toggle();
if ($ui.next('ul').is(':visible')) {
$ui.parent('li').removeClass('more').addClass('less');
} else {
$ui.parent('li').removeClass('less').addClass('more');
}
// eslint-disable-next-line
return false;
};
this.find('li > ul').each((i, item) => {
const $inputWrapper = $(item).prev('div');
$inputWrapper.on('click', clickHandler);
$inputWrapper.find('label').on('click', clickHandler);
if ($(item).is(':visible')) {
$(item).parent('li').removeClass('more').addClass('less');
} else {
$(item).parent('li').removeClass('less').addClass('more');
}
});
}
// return the jquery selection (or if it was a method call that returned a value - the returned value)
return returnValue;
};
}(jQuery));

View File

@@ -0,0 +1,44 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Toggle a class on $mainMenu after the end of an event (transition, animation...)
* @param {jQuery element} $navBar - The navbar item which get a css transition property.
* @param {jQuery element} $mainMenu - The menu inside the $navBar element.
* @param {string} endTransitionEvent - The name of the event.
* @param {jQuery element} $body - The body of the page.
* @method showNavBarContent - Toggle the class based on event and if body got a class.
* @method toggle - Add the listener if there is no transition launched yet.
* @return {Object} The object with methods wich permit to toggle on specific event.
*/
// eslint-disable-next-line
function NavbarTransitionHandler($navBar, $mainMenu, endTransitionEvent, $body) {
this.$body = $body;
this.transitionFired = false;
this.$navBar = $navBar.get(0);
this.$mainMenu = $mainMenu;
this.endTransitionEvent = endTransitionEvent;
this.showNavBarContent = (event) => {
if (event.propertyName !== 'width') {
return;
}
this.$navBar.removeEventListener(this.endTransitionEvent, this.showNavBarContent);
const isSidebarClosed = this.$body.hasClass('page-sidebar-closed');
this.$mainMenu.toggleClass('sidebar-closed', isSidebarClosed);
this.transitionFired = false;
};
this.toggle = () => {
if (!this.transitionFired) {
this.$navBar.addEventListener(this.endTransitionEvent, this.showNavBarContent.bind(this));
} else {
this.$navBar.removeEventListener(this.endTransitionEvent, this.showNavBarContent);
}
this.transitionFired = !this.transitionFired;
};
}

View File

@@ -0,0 +1,89 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Default layout instanciation
*/
$(function () {
const $this = $(this);
const $ajaxSpinner = $('.ajax-spinner');
$('[data-toggle="tooltip"]').tooltip();
rightSidebar.init();
/** spinner loading */
$this.ajaxStart(() => {
$ajaxSpinner.show();
});
$this.ajaxStop(() => {
$ajaxSpinner.hide();
});
$this.ajaxError(() => {
$ajaxSpinner.hide();
});
});
const rightSidebar = (function () {
return {
init() {
$('.btn-sidebar').on('click', function initLoadQuickNav() {
$('div.right-sidebar-flex').removeClass('col-lg-12').addClass('col-lg-9');
/** Lazy load of sidebar */
const url = $(this).data('url');
const target = $(this).data('target');
if (url) {
rightSidebar.loadQuickNav(url, target);
}
});
$(document).on('hide.bs.sidebar', () => {
$('div.right-sidebar-flex').removeClass('col-lg-9').addClass('col-lg-12');
});
},
loadQuickNav(url, target) {
/** Loads inner HTML in the sidebar container */
$(target).load(url, function () {
$(this).removeAttr('data-url');
$('ul.pagination > li > a[href]', this).on('click', (e) => {
e.preventDefault();
rightSidebar.navigationChange($(e.target).attr('href'), $(target));
});
$('ul.pagination > li > input[name="paginator_jump_page"]', this).on('keyup', function (e) {
if (e.which === 13) { // ENTER
e.preventDefault();
const val = parseInt($(e.target).val(), 10);
const limit = $(e.target).attr('pslimit');
const newUrl = $(this).attr('psurl').replace(/999999/, (val - 1) * limit);
rightSidebar.navigationChange(newUrl, $(target));
}
});
});
},
navigationChange(url, sidebar) {
rightSidebar.loadQuickNav(url, sidebar);
},
};
}());
/**
* BO Events Handler
*/
// eslint-disable-next-line
window.BOEvent = {
on(eventName, callback, context) {
document.addEventListener(eventName, (event) => {
if (typeof context !== 'undefined') {
callback.call(context, event);
} else {
callback(event);
}
});
},
emitEvent(eventName, eventType) {
const event = document.createEvent(eventType);
// true values stand for: can bubble, and is cancellable
event.initEvent(eventName, true, true);
document.dispatchEvent(event);
},
};

View File

@@ -0,0 +1,61 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* modal confirmation management
*/
window.modalConfirmation = (function () {
const modal = $('#confirmation_modal');
if (!modal) {
throw new Error('Modal confirmation is not available');
}
let actionsCallbacks = {
onCancel() {
console.log('modal canceled');
},
onContinue() {
console.log('modal continued');
},
};
modal.find('button.cancel').on('click', () => {
if (typeof actionsCallbacks.onCancel === 'function') {
actionsCallbacks.onCancel();
}
modalConfirmation.hide();
});
modal.find('button.continue').on('click', () => {
if (typeof actionsCallbacks.onContinue === 'function') {
actionsCallbacks.onContinue();
}
modalConfirmation.hide();
});
return {
init: function init() {},
create: function create(content, title, callbacks) {
if (title != null) {
modal.find('.modal-title').html(title);
}
if (content != null) {
modal.find('.modal-body').html(content);
}
actionsCallbacks = callbacks;
return this;
},
show: function show() {
modal.modal('show');
},
hide: function hide() {
modal.modal('hide');
},
};
}());
BOEvent.on('Modal confirmation started', () => {
modalConfirmation.init();
}, 'Back office');

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.
*/
$(() => {
/*
* Link action on the select list in the navigator toolbar.
* When change occurs, the page is refreshed (location.href redirection)
*/
$('select[name="paginator_select_page_limit"]').on('change', function () {
const url = $(this).attr('psurl').replace(/_limit/, $('option:selected', this).val());
window.location.href = url;
return false;
});
/*
* Input field changes management
*/
// eslint-disable-next-line
function checkInputPage(eventOrigin) {
const e = eventOrigin || event;
// eslint-disable-next-line
const char = e.type === 'keypress' ? String.fromCharCode(e.keyCode || e.which) : (e.clipboardData || window.clipboardData).getData('Text');
if (/[^\d]/gi.test(char)) {
return false;
}
}
$('input[name="paginator_jump_page"]').each(function () {
this.onkeypress = checkInputPage;
this.onpaste = checkInputPage;
// eslint-disable-next-line
$(this).on('keyup', function (e) {
const val = parseInt($(e.target).val(), 10);
if (e.which === 13) { // ENTER
e.preventDefault();
if (val > 0) {
const limit = $(e.target).attr('pslimit');
const url = $(this).attr('psurl').replace(/999999/, (val - 1) * limit);
window.location.href = url;
return false;
}
}
const max = parseInt($(e.target).attr('psmax'), 10);
if (val > max) {
$(this).val(max);
return false;
}
});
// eslint-disable-next-line
$(this).on('blur', function (e) {
const val = parseInt($(e.target).val(), 10);
if (parseInt(val, 10) > 0) {
const limit = $(e.target).attr('pslimit');
const url = $(this).attr('psurl').replace(/999999/, (val - 1) * limit);
window.location.href = url;
return false;
}
});
});
});

View File

@@ -0,0 +1,258 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
(function ($) {
let config = null;
const validateKeyCode = 13;
let tagsList = [];
const fullTagsString = null;
let pstaggerInput = null;
const defaultConfig = {
/* Global css config */
wrapperClassAdditional: '',
/* Tags part */
tagsWrapperClassAdditional: '',
tagClassAdditional: '',
closingCrossClassAdditionnal: '',
/* Tag Input part */
tagInputWrapperClassAdditional: '',
tagInputClassAdditional: '',
/* Global configuration */
delimiter: ' ',
inputPlaceholder: 'Add tag ...',
closingCross: true,
context: null,
clearAllBtn: false,
clearAllIconClassAdditional: '',
clearAllSpanClassAdditional: '',
/* Callbacks */
onTagsChanged: null,
onResetTags: null,
};
const immutableConfig = {
/* Global css config */
wrapperClass: 'pstaggerWrapper',
/* Tags part */
tagsWrapperClass: 'pstaggerTagsWrapper',
tagClass: 'pstaggerTag',
/* Tag Input part */
tagInputWrapperClass: 'pstaggerAddTagWrapper',
tagInputClass: 'pstaggerAddTagInput',
clearAllIconClass: '',
clearAllSpanClass: 'pstaggerResetTagsBtn',
closingCrossClass: 'pstaggerClosingCross',
};
const bindValidationInputEvent = function () {
// Validate input whenever validateKeyCode is pressed
pstaggerInput.keypress((event) => {
if (event.keyCode == validateKeyCode) {
tagsList = [];
processInput();
}
});
// If focusout of input, display tagsWrapper if not empty or leave input as is
pstaggerInput.focusout((event) => {
// Necessarry to avoid race condition when focusout input because we want to reset :-)
if ($(`.${immutableConfig.clearAllSpanClass}:hover`).length) {
return false;
}
// Only redisplay tags on focusOut if there's something in tagsList
if (pstaggerInput.val().length) {
tagsList = [];
processInput();
}
});
};
var processInput = function () {
const fullTagsStringRaw = pstaggerInput.val();
const tagsListRaw = fullTagsStringRaw.split(config.delimiter);
// Check that's not an empty input
if (fullTagsStringRaw.length) {
// Loop over each tags we got this round
for (var key in tagsListRaw) {
const tagRaw = tagsListRaw[key];
// No empty values
if (tagRaw === '') {
continue;
}
// Add tag into persistent list
tagsList.push(tagRaw);
}
let spanTagsHtml = '';
// Create HTML dom from list of tags we have
for (key in tagsList) {
const tag = tagsList[key];
spanTagsHtml += formatSpanTag(tag);
}
// Delete previous if any, then add recreated html content
$(`.${immutableConfig.tagsWrapperClass}`).empty().prepend(spanTagsHtml).css('display', 'block');
// Hide input until user click on tagify_tags_wrapper
$(`.${immutableConfig.tagInputWrapperClass}`).css('display', 'none');
} else {
$(`.${immutableConfig.tagsWrapperClass}`).css('display', 'none');
$(`.${immutableConfig.tagInputWrapperClass}`).css('display', 'block');
pstaggerInput.focus();
}
// Call the callback ! (if one)
if (config.onTagsChanged !== null) {
config.onTagsChanged.call(config.context, tagsList);
}
};
var formatSpanTag = function (tag) {
let spanTag = `<span class="${immutableConfig.tagClass} ${config.tagClassAdditional}">`
+ `<span>${
$('<div/>').text(tag).html()
}</span>`;
// Add closingCross if set to true
if (config.closingCross === true) {
spanTag += `<a class="${immutableConfig.closingCrossClass} ${config.closingCrossClassAdditionnal}" href="#">x</a>`;
}
spanTag += '</span>';
return spanTag;
};
const constructTagInputForm = function () {
// First hide native input
config.originalInput.css('display', 'none');
let addClearBtnHtml = '';
// If reset button required add it following user decription
if (config.clearAllBtn === true) {
addClearBtnHtml += `<span class="${immutableConfig.clearAllSpanClass} ${config.clearAllSpanClassAdditional}">`
+ `<i class="${immutableConfig.clearAllIconClass} ${config.clearAllIconClassAdditional}">clear</i>`
+ '</span>';
// Bind the click on the reset icon
bindResetTagsEvent();
}
// Add Tagify form after it
const formHtml = `<div class="${immutableConfig.wrapperClass} ${config.wrapperClassAdditional}">${
addClearBtnHtml
}<div class="${immutableConfig.tagsWrapperClass} ${config.tagsWrapperClassAdditional}"></div>`
+ `<div class="${immutableConfig.tagInputWrapperClass} ${config.tagInputWrapperClassAdditional}">`
+ `<input class="${immutableConfig.tagInputClass} ${config.tagInputClassAdditional}">`
+ '</div>'
+ '</div>';
// Insert form after the originalInput
config.originalInput.after(formHtml);
// Save tagify input in our object
pstaggerInput = $(`.${immutableConfig.tagInputClass}`);
// Add placeholder on tagify's input
pstaggerInput.attr('placeholder', config.inputPlaceholder);
return true;
};
const bindFocusInputEvent = function () {
// Bind click on tagsWrapper to switch and focus on input
$(`.${immutableConfig.tagsWrapperClass}`).on('click', (event) => {
const clickedElementClasses = event.target.className;
// Regexp to check if not clicked on closingCross to avoid focusing input if so
const checkClosingCrossRegex = new RegExp(immutableConfig.closingCrossClass, 'g');
const closingCrossClicked = clickedElementClasses.match(checkClosingCrossRegex);
if ($(`.${immutableConfig.tagInputWrapperClass}`).is(':hidden') && closingCrossClicked === null) {
$(`.${immutableConfig.tagsWrapperClass}`).css('display', 'none');
$(`.${immutableConfig.tagInputWrapperClass}`).css('display', 'block');
pstaggerInput.focus();
}
});
};
var bindResetTagsEvent = function () {
// Use delegate since we bind it before we insert the html in the DOM
const _this = this;
$(document).delegate(`.${immutableConfig.clearAllSpanClass}`, 'click', () => {
resetTags(true);
});
};
var resetTags = function (withCallback) {
// Empty tags list and tagify input
tagsList = [];
pstaggerInput.val('');
$(`.${immutableConfig.tagsWrapperClass}`).css('display', 'none');
$(`.${immutableConfig.tagInputWrapperClass}`).css('display', 'block');
pstaggerInput.focus();
// Empty existing Tags
$(`.${immutableConfig.tagClass}`).remove();
// Call the callback if one !
if (config.onResetTags !== null && withCallback === true) {
config.onResetTags.call(config.context);
}
};
const bindClosingCrossEvent = function () {
$(document).delegate(`.${immutableConfig.closingCrossClass}`, 'click', function (event) {
const thisTagWrapper = $(this).parent();
const clickedTagIndex = thisTagWrapper.index();
// Iterate through tags to reconstruct new pstaggerInput value
const newInputValue = reconstructInputValFromRemovedTag(clickedTagIndex);
// Apply new input value
pstaggerInput.val(newInputValue);
thisTagWrapper.remove();
tagsList = [];
processInput();
});
};
var reconstructInputValFromRemovedTag = function (clickedTagIndex) {
let finalStr = '';
$(`.${immutableConfig.tagClass}`).each(function (index, value) {
// If this is the tag we want to remove then continue else add to return string val
if (clickedTagIndex == $(this).index()) {
// jQuery.each() continue;
return true;
}
// Add to return value
finalStr += ` ${$(this).children().first().text()}`;
});
return finalStr;
};
const getTagsListOccurencesCount = function () {
const obj = {};
for (let i = 0, j = tagsList.length; i < j; i++) {
obj[tagsList[i]] = (obj[tagsList[i]] || 0) + 1;
}
return obj;
};
const setConfig = function (givenConfig, originalObject) {
const finalConfig = {};
// Loop on each default values, check if one given by user, if so -> override default
for (const property in defaultConfig) {
if (givenConfig.hasOwnProperty(property)) {
finalConfig[property] = givenConfig[property];
} else {
finalConfig[property] = defaultConfig[property];
}
}
finalConfig.originalInput = originalObject;
return finalConfig;
};
// jQuery extends function
$.fn.pstagger = function (_config) {
config = setConfig(_config, this);
constructTagInputForm();
bindValidationInputEvent();
bindFocusInputEvent();
bindClosingCrossEvent();
return {
resetTags,
};
};
}(jQuery));

View File

@@ -0,0 +1,412 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/* eslint-disable no-unused-vars, no-unreachable */
const {$} = window;
$(() => {
const form = $('form#product_catalog_list');
/*
* Tree behavior: collapse/expand system and radio button change event.
*/
$('div#product_catalog_category_tree_filter').categorytree();
$('div#product_catalog_category_tree_filter div.radio > label > input:radio').on('change', function () {
if ($(this).is(':checked')) {
$('form#product_catalog_list input[name="filter_category"]').val($(this).val());
$('form#product_catalog_list').submit();
}
});
$('div#product_catalog_category_tree_filter ~ div button, div#product_catalog_category_tree_filter ul')
.on('click', () => {
categoryFilterButtons();
});
categoryFilterButtons();
/*
* Click on a column header ordering icon to change orderBy / orderWay (location.href redirection)
*/
$('[psorderby][psorderway]', form).on('click', function () {
const orderBy = $(this).attr('psorderby');
const orderWay = $(this).attr('psorderway');
productOrderTable(orderBy, orderWay);
});
/*
* Checkboxes behavior with bulk actions
*/
$('input:checkbox[name="bulk_action_selected_products[]"]', form).on('change', () => {
updateBulkMenu();
});
/*
* Filter columns inputs behavior
*/
$('tr.column-filters input:text, tr.column-filters select', form).on('change input', () => {
productCatalogFilterChanged = true;
updateFilterMenu();
});
/*
* Sortable case when ordered by position ASC
*/
$('body').on('mousedown', 'tbody.sortable [data-uniturl] td.placeholder', function () {
const trParent = $(this).closest('tr');
trParent.find('input:checkbox[name="bulk_action_selected_products[]"]').attr('checked', true);
});
$('tbody.sortable', form).sortable({
placeholder: 'placeholder',
update(event, ui) {
const positionSpan = $('span.position', ui.item)[0];
$(positionSpan).css('color', 'red');
bulkProductEdition(event, 'sort');
},
});
/*
* Form submit pre action
*/
form.on('submit', function (e) {
e.preventDefault();
$('#filter_column_id_product', form).val($('#filter_column_id_product', form).attr('sql'));
$('#filter_column_price', form).val($('#filter_column_price', form).attr('sql'));
$('#filter_column_sav_quantity', form).val($('#filter_column_sav_quantity', form).attr('sql'));
productCatalogFilterChanged = false;
this.submit();
return false;
});
/*
* Send to SQL manager button on modal
*/
$('#catalog_sql_query_modal button[value="sql_manager"]').on('click', () => {
sendLastSqlQuery(createSqlQueryName());
});
updateBulkMenu();
updateFilterMenu();
/** create keyboard event for save & new */
jwerty.key('ctrl+P', (e) => {
e.preventDefault();
const url = $('form#product_catalog_list').attr('newproducturl');
window.location.href = url;
});
});
function productOrderTable(orderBy, orderWay) {
const form = $('form#product_catalog_list');
const url = form.attr('orderingurl').replace(/name/, orderBy).replace(/asc/, orderWay);
window.location.href = url;
}
// eslint-disable-next-line
function productOrderPrioritiesTable() {
window.location.href = $('form#product_catalog_list').attr('orderingurl');
}
function updateBulkMenu() {
// eslint-disable-next-line
const selectedCount = $('form#product_catalog_list input:checked[name="bulk_action_selected_products[]"][disabled!="disabled"]').length;
$('#product_bulk_menu').prop('disabled', (selectedCount === 0));
}
let productCatalogFilterChanged = false;
function updateFilterMenu() {
const columnFilters = $('#product_catalog_list').find('tr.column-filters');
let count = columnFilters.find('option:selected[value!=""]').length;
columnFilters.find('input[type="text"][sql!=""][sql], input[type="text"]:visible').each(function () {
if ($(this).val() !== '') {
count += 1;
}
});
const filtersNotUpdatedYet = (count === 0 && productCatalogFilterChanged === false);
$('button[name="products_filter_submit"]').prop('disabled', filtersNotUpdatedYet);
$('button[name="products_filter_reset"]').toggle(!filtersNotUpdatedYet);
}
function productCategoryFilterReset(div) {
$('#product_categories').categorytree('unselect');
$('#product_catalog_list input[name="filter_category"]').val('');
$('#product_catalog_list').submit();
}
function productCategoryFilterExpand(div, btn) {
$('#product_categories').categorytree('unfold');
}
function productCategoryFilterCollapse(div, btn) {
$('#product_categories').categorytree('fold');
}
function categoryFilterButtons() {
const catTree = $('#product_catalog_category_tree_filter');
const catTreeSiblingDivs = $('#product_catalog_category_tree_filter ~ div');
const catTreeList = catTree.find('ul ul');
catTreeSiblingDivs.find('button[name="product_catalog_category_tree_filter_collapse"]')
.toggle(!catTreeList.filter(':visible').length);
catTreeSiblingDivs.find('button[name="product_catalog_category_tree_filter_expand"]')
.toggle(!catTreeList.filter(':hidden').length);
catTreeSiblingDivs.find('button[name="product_catalog_category_tree_filter_reset"]')
.toggle(!catTree.find('ul input:checked').length);
}
function productColumnFilterReset(tr) {
$('input:text', tr).val('');
$('select option:selected', tr).prop('selected', false);
$('#filter_column_price', tr).attr('sql', '');
$('#filter_column_sav_quantity', tr).attr('sql', '');
$('#filter_column_id_product', tr).attr('sql', '');
$('#product_catalog_list').submit();
}
function bulkModalAction(allItems, postUrl, redirectUrl, action) {
const itemsCount = allItems.length;
let currentItemIdx = 0;
if (itemsCount < 1) {
return;
}
const targetModal = $(`#catalog_${action}_modal`);
targetModal.modal('show');
const details = targetModal.find(`#catalog_${action}_progression .progress-details-text`);
const progressBar = targetModal.find(`#catalog_${action}_progression .progress-bar`);
const failure = targetModal.find(`#catalog_${action}_failure`);
// re-init popup
details.html(details.attr('default-value'));
progressBar.css('width', '0%');
progressBar.find('span').html('');
progressBar.removeClass('progress-bar-danger');
progressBar.addClass('progress-bar-success');
failure.hide();
// call in ajax. Recursive with inner function
const bulkCall = function (items, successCallback, errorCallback) {
if (items.length === 0) {
return;
}
const item0 = $(items.shift()).val();
currentItemIdx += 1;
details.html(`${details.attr('default-value').replace(/\.\.\./, '')} (#${item0})`);
$.ajax({
type: 'POST',
url: postUrl,
data: {bulk_action_selected_products: [item0]},
success(data, status) {
// eslint-disable-next-line
progressBar.css('width', `${currentItemIdx * 100 / itemsCount}%`);
progressBar.find('span').html(`${currentItemIdx} / ${itemsCount}`);
if (items.length > 0) {
bulkCall(items, successCallback, errorCallback);
} else {
successCallback();
}
},
error: errorCallback,
dataType: 'json',
});
};
bulkCall(allItems.toArray(), () => {
window.location.href = redirectUrl;
}, () => {
progressBar.removeClass('progress-bar-success');
progressBar.addClass('progress-bar-danger');
failure.show();
window.location.href = redirectUrl;
});
}
function bulkProductAction(element, action) {
const form = $('#product_catalog_list');
let postUrl = '';
let redirectUrl = '';
let urlHandler = null;
const items = $('input:checked[name="bulk_action_selected_products[]"]', form);
if (items.length === 0) {
return false;
}
urlHandler = $(element).closest('[bulkurl]');
switch (action) {
case 'delete_all':
postUrl = urlHandler.attr('bulkurl').replace(/activate_all/, action);
redirectUrl = urlHandler.attr('redirecturl');
// Confirmation popup and callback...
$('#catalog_deletion_modal').modal('show');
$('#catalog_deletion_modal button[value="confirm"]').off('click');
$('#catalog_deletion_modal button[value="confirm"]').on('click', () => {
$('#catalog_deletion_modal').modal('hide');
return bulkModalAction(items, postUrl, redirectUrl, action);
});
return true; // No break, but RETURN, to avoid code after switch block :)
case 'activate_all':
postUrl = urlHandler.attr('bulkurl');
redirectUrl = urlHandler.attr('redirecturl');
return bulkModalAction(items, postUrl, redirectUrl, action);
break;
case 'deactivate_all':
postUrl = urlHandler.attr('bulkurl').replace(/activate_all/, action);
redirectUrl = urlHandler.attr('redirecturl');
return bulkModalAction(items, postUrl, redirectUrl, action);
break;
case 'duplicate_all':
postUrl = urlHandler.attr('bulkurl').replace(/activate_all/, action);
redirectUrl = urlHandler.attr('redirecturl');
return bulkModalAction(items, postUrl, redirectUrl, action);
break;
// this case will brings to the next page
case 'edition_next':
redirectUrl = $(element).closest('[massediturl]').attr('redirecturlnextpage');
// no break !
// this case will post inline edition command
// eslint-disable-next-line
case 'edition':
// eslint-disable-next-line
let editionAction;
// eslint-disable-next-line
const bulkEditionSelector = '#bulk_edition_toolbar input:submit';
if ($(bulkEditionSelector).length > 0) {
editionAction = $(bulkEditionSelector).attr('editionaction');
} else {
editionAction = 'sort';
}
urlHandler = $('[massediturl]');
postUrl = urlHandler.attr('massediturl').replace(/sort/, editionAction);
if (redirectUrl === '') {
redirectUrl = urlHandler.attr('redirecturl');
}
break;
// unknown cases...
default:
return false;
}
if (postUrl !== '' && redirectUrl !== '') {
// save action URL for redirection and update to post to bulk action instead
// using form action URL allow to get route attributes and stay on the same page & ordering.
const redirectionInput = $('<input>')
.attr('type', 'hidden')
.attr('name', 'redirect_url').val(redirectUrl);
form.append($(redirectionInput));
form.attr('action', postUrl);
form.submit();
}
return false;
}
function unitProductAction(element, action) {
const form = $('form#product_catalog_list');
// save action URL for redirection and update to post to bulk action instead
// using form action URL allow to get route attributes and stay on the same page & ordering.
const urlHandler = $(element).closest('[data-uniturl]');
const redirectUrlHandler = $(element).closest('[redirecturl]');
const redirectionInput = $('<input>')
.attr('type', 'hidden')
.attr('name', 'redirect_url').val(redirectUrlHandler.attr('redirecturl'));
// eslint-disable-next-line
switch (action) {
case 'delete':
// Confirmation popup and callback...
$('#catalog_deletion_modal').modal('show');
$('#catalog_deletion_modal button[value="confirm"]').off('click');
$('#catalog_deletion_modal button[value="confirm"]').on('click', () => {
form.append($(redirectionInput));
const url = urlHandler.attr('data-uniturl').replace(/duplicate/, action);
form.attr('action', url);
form.submit();
$('#catalog_deletion_modal').modal('hide');
});
return;
// Other cases, nothing to do, continue.
// default:
}
form.append($(redirectionInput));
const url = urlHandler.attr('data-uniturl').replace(/duplicate/, action);
form.attr('action', url);
form.submit();
}
function showBulkProductEdition(show) {
// Paginator does not have a next page link : we are on the last page!
if ($('a#pagination_next_url[href]').length === 0) {
$('#bulk_edition_save_next').prop('disabled', true).removeClass('btn-primary');
$('#bulk_edition_save_keep').attr('type', 'submit').addClass('btn-primary');
}
if (show) {
$('#bulk_edition_toolbar').show();
} else {
$('#bulk_edition_toolbar').hide();
}
}
function bulkProductEdition(element, action) {
const form = $('form#product_catalog_list');
// eslint-disable-next-line
switch (action) {
case 'sort':
showBulkProductEdition(true);
$('input#bulk_action_select_all, input:checkbox[name="bulk_action_selected_products[]"]', form)
.prop('disabled', true);
$('#bulk_edition_toolbar input:submit').attr('editionaction', action);
break;
case 'cancel':
// quantity inputs
$('td.product-sav-quantity', form).each(function () {
$(this).html($(this).attr('productquantityvalue'));
});
$('#bulk_edition_toolbar input:submit').removeAttr('editionaction');
showBulkProductEdition(false);
$('input#bulk_action_select_all, input:checkbox[name="bulk_action_selected_products[]"]', form)
.prop('disabled', false);
break;
}
}
function showLastSqlQuery() {
$('#catalog_sql_query_modal_content textarea[name="sql"]').val($('tbody[last_sql]').attr('last_sql'));
$('#catalog_sql_query_modal').modal('show');
}
function sendLastSqlQuery(name) {
$('#catalog_sql_query_modal_content textarea[name="sql"]').val($('tbody[last_sql]').attr('last_sql'));
$('#catalog_sql_query_modal_content input[name="name"]').val(name);
$('#catalog_sql_query_modal_content').submit();
}

View File

@@ -0,0 +1,51 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Default category management
*/
const defaultCategory = (function () {
const defaultCategoryForm = $('#form_step1_id_category_default');
return {
init() {
// Populate category tree with the default category
const defaultCategoryId = defaultCategoryForm.find('input:checked').val();
productCategoriesTags.checkDefaultCategory(defaultCategoryId);
/** Hide the default form, if javascript disabled it will be visible and so we
* still can select a default category using the form
*/
defaultCategoryForm.hide();
},
/**
* Check the radio bouton with the selected value
*/
check(value) {
defaultCategoryForm.find(`input[value="${value}"]`).prop('checked', true);
},
isChecked(value) {
return defaultCategoryForm.find(`input[value="${value}"]`).is(':checked');
},
/**
* When the category selected as a default is unselected
* The default category MUST be a selected category
*/
reset() {
const firstInput = defaultCategoryForm.find('input:first-child');
firstInput.prop('checked', true);
const categoryId = firstInput.val();
productCategoriesTags.checkDefaultCategory(categoryId);
},
};
}());
window.defaultCategory = defaultCategory;
BOEvent.on('Product Default category Management started', () => {
defaultCategory.init();
}, 'Back office');

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,213 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Product categories Tags management
*/
const productCategoriesTags = (function () {
const defaultCategoryForm = $('#form_step1_id_category_default');
const categoriesForm = $('#form_step1_categories');
const tagsContainer = $('#ps_categoryTags');
return {
init() {
selectedCategories = this.getTags();
selectedCategories.forEach(this.createTag);
// add tags management
this.manageTagsOnInput();
this.manageTagsOnTags();
// add default category management
this.checkDefaultCategory();
// add search box
this.initSearchBox();
},
removeTag(categoryId) {
$(`span[data-id^="${categoryId}"]`).parent().remove();
return true;
},
getTags() {
const firstStepCategoriesForm = $('#form_step1_categories');
const inputs = firstStepCategoriesForm.find('label > input[type=checkbox]:checked').toArray();
const tags = [];
const that = this;
inputs.forEach((input) => {
const tree = that.getTree();
const tag = {
name: input.parentNode.innerText,
id: input.value,
};
tree.forEach((_category) => {
if (_category.id === tag.id) {
tag.breadcrumb = _category.breadcrumb;
}
});
tags.push(tag);
});
return tags;
},
manageTagsOnInput() {
const firstStepCategoriesForm = $('#form_step1_categories');
const that = this;
firstStepCategoriesForm.on('change', 'input[type=checkbox]', function () {
const input = $(this);
if (input.prop('checked') === false) {
that.removeTag($(this).val());
} else {
const tag = {
name: input.parent().text(),
id: input.val(),
breadcrumb: '',
};
that.createTag(tag);
}
});
return true;
},
manageTagsOnTags() {
const that = this;
tagsContainer.on('click', 'a.pstaggerClosingCross', function (event) {
event.preventDefault();
const id = $(this).data('id');
that.removeTag(id);
categoriesForm.find(`input[value="${id}"].category`).prop('checked', false);
tagsContainer.focus();
});
return true;
},
checkDefaultCategory(categoryId) {
const firstStepCategoriesForm = $('#form_step1_categories');
const selector = `input[value="${categoryId}"].default-category`;
firstStepCategoriesForm.find(selector).prop('checked', true);
},
getTree() {
const tree = JSON.parse($('#ps_categoryTree').html());
return tree;
},
createTag(category) {
if (category.breadcrumb === '') {
const tree = this.getTree();
tree.forEach((_category) => {
if (_category.id === category.id) {
category.breadcrumb = _category.breadcrumb;
}
});
}
const isTagExist = tagsContainer.find(`span[data-id=${category.id}]`);
if (isTagExist.length === 0) {
tagsContainer.append(`${'<span class="pstaggerTag">'
+ '<span data-id="'}${category.id}" title="${category.breadcrumb}">${category.name}</span>`
+ `<a class="pstaggerClosingCross" href="#" data-id="${category.id}">x</a>`
+ '</span>');
const optionId = `#form_step1_id_category_default_${category.id}`;
if ($(optionId).length === 0) {
defaultCategoryForm.append(`${'<div class="radio">'
+ '<label class="required">'
// eslint-disable-next-line
+ '<input type="radio"' + 'id="form_step1_id_category_default_'}${category.id}" name="form[step1][id_category_default]" required="required" value="${category.id}">${
category.name}</label>`
+ '</div>');
}
}
return true;
},
getNameFromBreadcrumb(name) {
if (name.indexOf('&gt;') !== -1) {
return name.substring(name.lastIndexOf('&gt') + 4); // remove "&gt; "
}
return name;
},
initSearchBox() {
const searchCategorySelector = '#ps-select-product-category';
const searchBox = $(searchCategorySelector);
const tree = this.getTree();
const tags = [];
const that = this;
let searchResultMsg = '';
tree.forEach((tagObject) => {
tags.push({
label: tagObject.breadcrumb,
value: tagObject.id,
});
});
// eslint-disable-next-line
searchBox.autocomplete({
source: tags,
minChars: 2,
autoFill: true,
max: 20,
matchContains: true,
mustMatch: false,
scroll: false,
focus(event, ui) {
event.preventDefault();
const $this = $(this);
$this.val(that.getNameFromBreadcrumb(ui.item.label));
searchResultMsg = $this.parent().find('[role=status]').text();
},
select(event, ui) {
event.preventDefault();
const {label} = ui.item;
const categoryName = that.getNameFromBreadcrumb(label);
const categoryId = ui.item.value;
that.createTag({
name: categoryName,
id: categoryId,
breadcrumb: label,
});
const firstStepCategoriesForm = $('#form_step1_categories');
firstStepCategoriesForm.find(`input[value="${categoryId}"].category`).prop('checked', true);
$(this).val('');
},
}).data('ui-autocomplete')._renderItem = function (ul, item) {
return $('<li>')
.data('ui-autocomplete-item', item)
.append(`<a>${item.label}</a>`)
.appendTo(ul);
};
searchBox.parent().find('[role=status]').on('DOMSubtreeModified', function () {
const $this = $(this);
if ($.isNumeric($this.text()) && searchResultMsg !== '' && searchBox.val() !== '') {
$this.text(searchResultMsg);
}
});
$('body').on('focusout', searchCategorySelector, (event) => {
const $searchInput = $(event.currentTarget);
if ($searchInput.val().length === 0) {
$searchInput.parent().find('[role=status]').text('');
searchResultMsg = '';
}
});
},
};
}());
window.productCategoriesTags = productCategoriesTags;
BOEvent.on('Product Categories Management started', () => {
productCategoriesTags.init();
}, 'Back office');

View File

@@ -0,0 +1,331 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Function for removing bad characters from localization formating.
*/
function replaceBadLocaleCharacters() {
// eslint-disable-next-line
$.each($('input.attribute_wholesale_price, input.attribute_priceTE, input.attribute_priceTI, input.attribute_unity, input.attribute_weight'), function () {
$(this).val($(this).val().replace('', '-')); // replace U+002D with U+2212
});
}
/**
* Combination management
*/
window.combinations = (function () {
/**
* Remove a combination
* @param {object} elem - The clicked link
*/
function remove(elem) {
const combinationElem = $(`#attribute_${elem.attr('data')}`);
// eslint-disable-next-line
window.modalConfirmation.create(translate_javascripts['Are you sure you want to delete this item?'], null, {
onContinue() {
// We need this because there is a specific data="smthg" attribute so we can't use data() function
const attributeId = elem.attr('data');
$.ajax({
type: 'DELETE',
data: {'attribute-ids': [attributeId]},
url: elem.attr('href'),
beforeSend() {
elem.attr('disabled', 'disabled');
$('#create-combinations, #apply-on-combinations, #submit, .btn-submit').attr('disabled', 'disabled');
},
success(response) {
refreshTotalCombinations(-1, 1);
combinationElem.remove();
showSuccessMessage(response.message);
displayFieldsManager.refresh();
},
error(response) {
showErrorMessage(jQuery.parseJSON(response.responseText).message);
},
complete() {
elem.removeAttr('disabled');
$('#create-combinations, #apply-on-combinations, #submit, .btn-submit').removeAttr('disabled');
supplierCombinations.refresh();
if ($('.js-combinations-list .combination').length <= 0) {
$('#combinations_thead').fadeOut();
}
},
});
},
}).show();
}
/**
* Update final price, regarding the impact on price in combinations table
* @param {jQuery} tableRow - Table row that contains the combination
*/
function updateFinalPrice(tableRow) {
if (!tableRow.is('tr')) {
throw new Error('Structure of table has changed, this function needs to be updated.');
}
// We need this because there is a specific data="smthg" attribute so we can't use data() function
const attributeId = tableRow.attr('data');
// Get combination final price value from combination form
const finalPrice = priceCalculation.getCombinationFinalPriceTaxExcludedById(attributeId);
const finalPriceLabel = tableRow.find('.attribute-finalprice span.final-price');
finalPriceLabel.html(finalPrice);
// Update ecotax preview (tax included)
let combinationEcotaxTI = priceCalculation.getCombinationEcotaxTaxIncludedById(attributeId);
if (combinationEcotaxTI === 0) {
combinationEcotaxTI = priceCalculation.getProductEcotaxTaxIncluded();
}
const ecoTaxLabel = tableRow.find('.attribute-finalprice span.attribute-ecotax');
ecoTaxLabel.html(Number(ps_round(combinationEcotaxTI, 2)).toFixed(2)); // 2 digits for short
const ecoTaxPreview = tableRow.find('.attribute-finalprice .attribute-ecotax-preview');
ecoTaxPreview.toggleClass('d-none', Number(combinationEcotaxTI) === 0);
}
/**
* Returns a reference to the form for a specific combination
* @param {String} attributeId
* @return {jQuery}
*/
function getCombinationForm(attributeId) {
return $(`#combination_form_${attributeId}`);
}
/**
* Returns a reference to the row of a specific combination
* @param {String} attributeId
* @return {jQuery}
*/
function getCombinationRow(attributeId) {
return $(`#accordion_combinations #attribute_${attributeId}`);
}
return {
init() {
const showVariationsSelector = '#show_variations_selector input';
const productTypeSelector = $('#form_step1_type_product');
const combinationsListSelector = '#accordion_combinations .combination';
let combinationsList = $(combinationsListSelector);
if (combinationsList.length > 0) {
productTypeSelector.prop('disabled', true);
}
$(document)
// delete combination
.on('click', '#accordion_combinations .delete', function (e) {
e.preventDefault();
remove($(this));
})
// when typing a new quantity on the form, update it on the row
.on('keyup', 'input[id^="combination"][id$="_attribute_quantity"]', function () {
const attributeId = $(this).closest('.combination-form').attr('data');
const input = getCombinationRow(attributeId).find('.attribute-quantity input');
input.val($(this).val());
})
// when typing a new quantity on the row, update it on the form
.on('keyup', '.attribute-quantity input', function () {
const attributeId = $(this).closest('.combination').attr('data');
const input = getCombinationForm(attributeId).find('input[id^="combination"][id$="_attribute_quantity"]');
input.val($(this).val());
})
.on({
// when typing a new impact on price on the form, update it on the row
keyup() {
const attributeId = $(this).closest('.combination-form').attr('data');
const input = getCombinationRow(attributeId).find('.attribute-price input');
input.val($(this).val());
},
// when impact on price on the form is changed, update final price
change() {
const attributeId = $(this).closest('.combination-form').attr('data');
const input = getCombinationRow(attributeId).find('.attribute-price input');
input.val($(this).val());
updateFinalPrice($(input.parents('tr')[0]));
},
}, 'input[id^="combination"][id$="_attribute_price"]')
.on({
// when ecotax on the form is changed, update final price
change() {
const attributeId = $(this).closest('.combination-form').attr('data');
const finalPriceLabel = getCombinationRow(attributeId).find('.attribute-finalprice span.final-price');
updateFinalPrice($(finalPriceLabel.parents('tr')[0]));
},
}, 'input[id^="combination"][id$="_attribute_ecotax"]')
// when price impact is changed on the row, update it on the form
.on('change', '.attribute-price input', function () {
const attributeId = $(this).closest('.combination').attr('data');
const input = getCombinationForm(attributeId).find('input[id^="combination"][id$="_attribute_price"]');
input.val($(this).val());
// Trigger keyup to update form final price
input.trigger('keyup');
updateFinalPrice($(this).parent().parent().parent());
})
// on change default attribute, update which combination is the new default
.on('click', 'input.attribute-default', function () {
const selectedCombination = $(this);
const combinationRadioButtons = $('input.attribute-default');
const attributeId = $(this).closest('.combination').attr('data');
combinationRadioButtons.each(function unselect() {
const combination = $(this);
if (combination.data('id') !== selectedCombination.data('id')) {
combination.prop('checked', false);
}
});
$('.attribute_default_checkbox').prop('checked', false);
getCombinationForm(attributeId)
.find('input[id^="combination"][id$="_attribute_default"]')
.prop('checked', true);
})
// Combinations fields display management
.on('change', showVariationsSelector, function () {
displayFieldsManager.refresh();
combinationsList = $(combinationsListSelector);
if ($(this).val() === '0') {
// if combination(s) exists, alert user for deleting it
if (combinationsList.length > 0) {
window.modalConfirmation.create(
translate_javascripts['Are you sure to disable variations ? they will all be deleted'], null, {
onCancel() {
$('#show_variations_selector input[value="1"]').prop('checked', true);
displayFieldsManager.refresh();
},
onContinue() {
$.ajax({
type: 'GET',
// eslint-disable-next-line
url: $('#accordion_combinations').attr('data-action-delete-all').replace(/\/\d+(?=\?.*)?/, `/${$('#form_id_product').val()}`),
success() {
combinationsList.remove();
displayFieldsManager.refresh();
},
error(response) {
showErrorMessage(jQuery.parseJSON(response.responseText).message);
},
});
// enable the top header selector
// we want to use a "Simple product" without any combinations
productTypeSelector.prop('disabled', false);
},
}).show();
} else {
// enable the top header selector if no combination(s) exists
productTypeSelector.prop('disabled', false);
}
} else {
// this means we have or we want to have combinations
// disable the product type selector
productTypeSelector.prop('disabled', true);
}
})
// open combination form
.on('click', '#accordion_combinations .btn-open', function (e) {
e.preventDefault();
const contentElem = $($(this).attr('href'));
/** create combinations navigation */
const navElem = contentElem.find('.nav');
const idAttribute = contentElem.attr('data');
const prevCombinationId = $(`#accordion_combinations tr[data="${idAttribute}"]`).prev().attr('data');
const nextCombinationId = $(`#accordion_combinations tr[data="${idAttribute}"]`).next().attr('data');
navElem.find('.prev, .next').hide();
if (prevCombinationId) {
navElem.find('.prev').attr('data', prevCombinationId).show();
}
if (nextCombinationId) {
navElem.find('.next').attr('data', nextCombinationId).show();
}
/** init combination tax include price */
replaceBadLocaleCharacters();
priceCalculation.impactTaxInclude(contentElem.find('.attribute_priceTE'));
priceCalculation.impactFinalPrice(contentElem.find('.attribute_priceTE'));
contentElem.insertBefore('#form-nav').removeClass('hide').show();
contentElem.find('.datepicker input[type="text"]').datetimepicker({
locale: iso_user,
format: 'YYYY-MM-DD',
});
function countSelectedProducts() {
return $(`#combination_form_${contentElem.attr('data')} .img-highlight`).length;
}
const number = $(`#combination_form_${contentElem.attr('data')} .number-of-images`);
// eslint-disable-next-line
const allProductCombination = $(`#combination_form_${contentElem.attr('data')} .product-combination-image`).length;
number.text(`${countSelectedProducts()}/${allProductCombination}`);
$(document).on('click', '.tabs .product-combination-image', () => {
number.text(`${countSelectedProducts()}/${allProductCombination}`);
});
/** Add title on product's combination image */
$(() => {
$(`#combination_form_${contentElem.attr('data')}`).find('img').each(function () {
title = $(this).attr('src').split('/').pop();
$(this).attr('title', title);
});
});
$('#form-nav, #form_content').hide();
})
// close combination form
.on('click', '#form .combination-form .btn-back', function (e) {
e.preventDefault();
$(this).closest('.combination-form').hide();
$('#form-nav, #form_content').show();
})
// switch combination form
.on('click', '#form .combination-form .nav a', function (e) {
e.preventDefault();
$('.combination-form').hide();
$(`#accordion_combinations .combination[data="${$(this).attr('data')}"] .btn-open`).click();
});
},
};
}());
BOEvent.on('Product Combinations Management started', () => {
combinations.init();
}, 'Back office');
/**
* Refresh bulk actions combination number after creating or deleting combinations
*
* @param {number} sign
* @param {number} number
*/
window.refreshTotalCombinations = function (sign, number) {
const $bulkCombinationsTotal = $('#js-bulk-combinations-total');
const currentnumber = parseInt($bulkCombinationsTotal.text(), 10) + (sign * number);
$bulkCombinationsTotal.text(currentnumber);
};

View File

@@ -0,0 +1,40 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Manufacturer management
*/
window.manufacturer = (function () {
return {
init() {
const addButton = $('#add_brand_button');
const resetButton = $('#reset_brand_product');
const manufacturerContent = $('#manufacturer-content');
const selectManufacturer = $('#form_step1_id_manufacturer');
/** Click event on the add button */
addButton.on('click', (e) => {
e.preventDefault();
manufacturerContent.removeClass('hide');
addButton.hide();
});
resetButton.on('click', (e) => {
e.preventDefault();
// eslint-disable-next-line
modalConfirmation.create(translate_javascripts['Are you sure you want to delete this item?'], null, {
onContinue() {
manufacturerContent.addClass('hide');
selectManufacturer.val('').trigger('change');
addButton.show();
},
}).show();
});
},
};
}());
// eslint-disable-next-line
BOEvent.on('Product Manufacturer Management started', () => {
manufacturer.init();
}, 'Back office');

View File

@@ -0,0 +1,47 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/**
* Related product management
*/
window.relatedProduct = (function () {
return {
init() {
const addButton = $('#add-related-product-button');
const resetButton = $('#reset_related_product');
const relatedContent = $('#related-content');
const productItems = $('#form_step1_related_products-data');
const searchProductsBar = $('#form_step1_related_products');
addButton.on('click', (e) => {
e.preventDefault();
relatedContent.removeClass('hide');
addButton.hide();
});
resetButton.on('click', (e) => {
e.preventDefault();
// eslint-disable-next-line
modalConfirmation.create(translate_javascripts['Are you sure you want to delete this item?'], null, {
onContinue: function onContinue() {
const items = productItems.find('li').toArray();
items.forEach((item) => {
console.log(item);
item.remove();
});
searchProductsBar.val('');
relatedContent.addClass('hide');
addButton.show();
},
}).show();
});
},
};
}());
// eslint-disable-next-line
BOEvent.on('Product Related Management started', () => {
relatedProduct.init();
}, 'Back office');

View File

@@ -0,0 +1,156 @@
/**
* For the full copyright and license information, please view the
* docs/licenses/LICENSE.txt file that was distributed with this source code.
*/
/* ========================================================================
* Bootstrap: sidebar.js v0.1
* ========================================================================
* Copyright 2011-2014 Asyraf Abdul Rahman
* Licensed under MIT
* ======================================================================== */
(function ($) {
// SIDEBAR PUBLIC CLASS DEFINITION
// ================================
const Sidebar = function (element, options) {
this.$element = $(element);
this.options = $.extend({}, Sidebar.DEFAULTS, options);
this.transitioning = null;
if (this.options.parent) {
this.$parent = $(this.options.parent);
}
if (this.options.toggle) {
this.toggle();
}
};
Sidebar.DEFAULTS = {
toggle: true,
};
Sidebar.prototype.show = function () {
if (this.transitioning || this.$element.hasClass('sidebar-open')) {
return;
}
const startEvent = $.Event('show.bs.sidebar');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) {
return;
}
this.$element
.addClass('sidebar-open');
this.transitioning = 1;
const complete = function () {
this.transitioning = 0;
this.$element.trigger('shown.bs.sidebar');
};
if (!$.support.transition) {
// eslint-disable-next-line
return complete.call(this);
}
this.$element
.one($.support.transition.end, $.proxy(complete, this))
.emulateTransitionEnd(400);
};
Sidebar.prototype.hide = function () {
if (this.transitioning || !this.$element.hasClass('sidebar-open')) {
return;
}
const startEvent = $.Event('hide.bs.sidebar');
this.$element.trigger(startEvent);
if (startEvent.isDefaultPrevented()) {
return;
}
this.$element
.removeClass('sidebar-open');
this.transitioning = 1;
const complete = function () {
this.transitioning = 0;
this.$element
.trigger('hidden.bs.sidebar');
};
if (!$.support.transition) {
// eslint-disable-next-line
return complete.call(this);
}
this.$element
.one($.support.transition.end, $.proxy(complete, this))
.emulateTransitionEnd(400);
};
Sidebar.prototype.toggle = function () {
this[this.$element.hasClass('sidebar-open') ? 'hide' : 'show']();
};
const old = $.fn.sidebar;
$.fn.sidebar = function (option) {
return this.each(function () {
const $this = $(this);
let data = $this.data('bs.sidebar');
const options = $.extend({}, Sidebar.DEFAULTS, $this.data(), typeof this.options === 'object' && option);
if (!data && options.toggle && option === 'show') {
// eslint-disable-next-line
option = !option;
}
if (!data) {
$this.data('bs.sidebar', (data = new Sidebar(this, options)));
}
if (typeof option === 'string') {
data[option]();
}
});
};
$.fn.sidebar.Constructor = Sidebar;
$.fn.sidebar.noConflict = function () {
$.fn.sidebar = old;
return this;
};
$(document).on('click.bs.sidebar.data-api', '[data-toggle="sidebar"]', function (e) {
const $this = $(this);
let href;
// eslint-disable-next-line
const target = $this.attr('data-target') || e.preventDefault() || (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '');
const $target = $(target);
const data = $target.data('bs.sidebar');
const option = data ? 'toggle' : $this.data();
$target.sidebar(option);
});
$('html').on('click.bs.sidebar.autohide', (event) => {
const $this = $(event.target);
/* eslint-disable */
const isButtonOrSidebar = $this.is('.sidebar, [data-toggle="sidebar"]') || $this.parents('.sidebar, [data-toggle="sidebar"]').length;
if (!isButtonOrSidebar) {
const $target = $('.sidebar');
$target.each((i, trgt) => {
const $trgt = $(trgt);
if ($trgt.data('bs.sidebar') && $trgt.hasClass('sidebar-open')) {
$trgt.sidebar('hide');
}
});
}
});
}(jQuery));

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.
*/
/**
* Get the correct transition keyword of the browser.
* @param {string} type - The property name (transition for example).
* @param {string} lifecycle - Which lifecycle of the property name to catch (end, start...).
* @return {string} The transition keywoard of the browser.
*/
// eslint-disable-next-line
function getAnimationEvent(type, lifecycle) {
const el = document.createElement('element');
const typeUpper = type.charAt(0).toUpperCase() + type.substring(1);
const lifecycleUpper = lifecycle.charAt(0).toUpperCase() + lifecycle.substring(1);
const properties = {
transition: `${type}${lifecycle}`,
OTransition: `o${typeUpper}${lifecycleUpper}`,
MozTransition: `${type}${lifecycle}`,
WebkitTransition: `webkit${typeUpper}${lifecycleUpper}`,
};
const key = Object.keys(properties).find((propKey) => el.style[propKey] !== undefined);
return key !== undefined ? properties[key] : false;
}