first commit
This commit is contained in:
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
export default class AutocompleteWithEmail {
|
||||
constructor(emailInputSelector, map = []) {
|
||||
this.map = map;
|
||||
this.$emailInput = $(emailInputSelector);
|
||||
this.$emailInput.on('change', () => this.change());
|
||||
}
|
||||
|
||||
change() {
|
||||
$.get({
|
||||
url: this.$emailInput.data('customer-information-url'),
|
||||
dataType: 'json',
|
||||
data: {
|
||||
email: this.$emailInput.val(),
|
||||
},
|
||||
}).then((response) => {
|
||||
Object.keys(this.map).forEach((key) => {
|
||||
if (response[key] !== undefined) {
|
||||
$(this.map[key]).val(response[key]);
|
||||
}
|
||||
});
|
||||
}).catch((response) => {
|
||||
if (typeof response.responseJSON !== 'undefined') {
|
||||
window.showErrorMessage(response.responseJSON.message);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,249 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
import ChangePasswordHandler from '../change-password-handler';
|
||||
import PasswordValidator from '../password-validator';
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Class responsible for actions related to "change password" form type.
|
||||
* Generates random passwords, validates new password and it's confirmation,
|
||||
* displays error messages related to validation.
|
||||
*/
|
||||
export default class ChangePasswordControl {
|
||||
constructor(
|
||||
inputsBlockSelector,
|
||||
showButtonSelector,
|
||||
hideButtonSelector,
|
||||
generatePasswordButtonSelector,
|
||||
oldPasswordInputSelector,
|
||||
newPasswordInputSelector,
|
||||
confirmNewPasswordInputSelector,
|
||||
generatedPasswordDisplaySelector,
|
||||
passwordStrengthFeedbackContainerSelector,
|
||||
) {
|
||||
// Block that contains password inputs
|
||||
this.$inputsBlock = $(inputsBlockSelector);
|
||||
|
||||
// Button that shows the password inputs block
|
||||
this.showButtonSelector = showButtonSelector;
|
||||
|
||||
// Button that hides the password inputs block
|
||||
this.hideButtonSelector = hideButtonSelector;
|
||||
|
||||
// Button that generates a random password
|
||||
this.generatePasswordButtonSelector = generatePasswordButtonSelector;
|
||||
|
||||
// Input to enter old password
|
||||
this.oldPasswordInputSelector = oldPasswordInputSelector;
|
||||
|
||||
// Input to enter new password
|
||||
this.newPasswordInputSelector = newPasswordInputSelector;
|
||||
|
||||
// Input to confirm the new password
|
||||
this.confirmNewPasswordInputSelector = confirmNewPasswordInputSelector;
|
||||
|
||||
// Input that displays generated random password
|
||||
this.generatedPasswordDisplaySelector = generatedPasswordDisplaySelector;
|
||||
|
||||
// Main input for password generation
|
||||
this.$newPasswordInputs = this.$inputsBlock
|
||||
.find(this.newPasswordInputSelector);
|
||||
|
||||
// Generated password will be copied to these inputs
|
||||
this.$copyPasswordInputs = this.$inputsBlock
|
||||
.find(this.confirmNewPasswordInputSelector)
|
||||
.add(this.generatedPasswordDisplaySelector);
|
||||
|
||||
// All inputs in the change password block, that are submittable with the form.
|
||||
this.$submittableInputs = this.$inputsBlock
|
||||
.find(this.oldPasswordInputSelector)
|
||||
.add(this.newPasswordInputSelector)
|
||||
.add(this.confirmNewPasswordInputSelector);
|
||||
|
||||
this.passwordHandler = new ChangePasswordHandler(
|
||||
passwordStrengthFeedbackContainerSelector,
|
||||
);
|
||||
|
||||
this.passwordValidator = new PasswordValidator(
|
||||
this.newPasswordInputSelector,
|
||||
this.confirmNewPasswordInputSelector,
|
||||
);
|
||||
|
||||
this.hideInputsBlock();
|
||||
this.initEvents();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize events.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
initEvents() {
|
||||
// Show the inputs block when show button is clicked
|
||||
$(document).on('click', this.showButtonSelector, (e) => {
|
||||
this.hide($(e.currentTarget));
|
||||
this.showInputsBlock();
|
||||
});
|
||||
|
||||
$(document).on('click', this.hideButtonSelector, () => {
|
||||
this.hideInputsBlock();
|
||||
this.show($(this.showButtonSelector));
|
||||
});
|
||||
|
||||
// Watch and display feedback about password's strength
|
||||
this.passwordHandler.watchPasswordStrength(this.$newPasswordInputs);
|
||||
|
||||
$(document).on('click', this.generatePasswordButtonSelector, () => {
|
||||
// Generate the password into main input.
|
||||
this.passwordHandler.generatePassword(this.$newPasswordInputs);
|
||||
|
||||
// Copy the generated password from main input to additional inputs
|
||||
this.$copyPasswordInputs.val(this.$newPasswordInputs.val());
|
||||
this.checkPasswordValidity();
|
||||
});
|
||||
|
||||
// Validate new password and it's confirmation when any of the inputs is changed
|
||||
$(document).on(
|
||||
'keyup',
|
||||
`${this.newPasswordInputSelector},${this.confirmNewPasswordInputSelector}`,
|
||||
() => {
|
||||
this.checkPasswordValidity();
|
||||
},
|
||||
);
|
||||
|
||||
// Prevent submitting the form if new password is not valid
|
||||
$(document).on('submit', $(this.oldPasswordInputSelector).closest('form'), (event) => {
|
||||
// If password input is disabled - we don't need to validate it.
|
||||
if ($(this.oldPasswordInputSelector).is(':disabled')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.passwordValidator.isPasswordValid()) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if password is valid, show error messages if it's not.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
checkPasswordValidity() {
|
||||
const $firstPasswordErrorContainer = $(this.newPasswordInputSelector).parent().find('.form-text');
|
||||
const $secondPasswordErrorContainer = $(this.confirmNewPasswordInputSelector).parent().find('.form-text');
|
||||
|
||||
$firstPasswordErrorContainer
|
||||
.text(this.getPasswordLengthValidationMessage())
|
||||
.toggleClass('text-danger', !this.passwordValidator.isPasswordLengthValid());
|
||||
$secondPasswordErrorContainer
|
||||
.text(this.getPasswordConfirmationValidationMessage())
|
||||
.toggleClass('text-danger', !this.passwordValidator.isPasswordMatchingConfirmation());
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password confirmation validation message.
|
||||
*
|
||||
* @returns {String}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPasswordConfirmationValidationMessage() {
|
||||
if (!this.passwordValidator.isPasswordMatchingConfirmation()) {
|
||||
return $(this.confirmNewPasswordInputSelector).data('invalid-password');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get password length validation message.
|
||||
*
|
||||
* @returns {String}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
getPasswordLengthValidationMessage() {
|
||||
if (this.passwordValidator.isPasswordTooShort()) {
|
||||
return $(this.newPasswordInputSelector).data('password-too-short');
|
||||
}
|
||||
|
||||
if (this.passwordValidator.isPasswordTooLong()) {
|
||||
return $(this.newPasswordInputSelector).data('password-too-long');
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Show the password inputs block.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
showInputsBlock() {
|
||||
this.show(this.$inputsBlock);
|
||||
this.$submittableInputs.removeAttr('disabled');
|
||||
this.$submittableInputs.attr('required', 'required');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide the password inputs block.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
hideInputsBlock() {
|
||||
this.hide(this.$inputsBlock);
|
||||
this.$submittableInputs.attr('disabled', 'disabled');
|
||||
this.$submittableInputs.removeAttr('required');
|
||||
this.$inputsBlock.find('input').val('');
|
||||
this.$inputsBlock.find('.form-text').text('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Hide an element.
|
||||
*
|
||||
* @param {jQuery} $el
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
hide($el) {
|
||||
$el.addClass('d-none');
|
||||
}
|
||||
|
||||
/**
|
||||
* Show hidden element.
|
||||
*
|
||||
* @param {jQuery} $el
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
show($el) {
|
||||
$el.removeClass('d-none');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Handles UI interactions of choice tree
|
||||
*/
|
||||
export default class ChoiceTree {
|
||||
/**
|
||||
* @param {String} treeSelector
|
||||
*/
|
||||
constructor(treeSelector) {
|
||||
this.$container = $(treeSelector);
|
||||
|
||||
this.$container.on('click', '.js-input-wrapper', (event) => {
|
||||
const $inputWrapper = $(event.currentTarget);
|
||||
|
||||
this.toggleChildTree($inputWrapper);
|
||||
});
|
||||
|
||||
this.$container.on('click', '.js-toggle-choice-tree-action', (event) => {
|
||||
const $action = $(event.currentTarget);
|
||||
|
||||
this.toggleTree($action);
|
||||
});
|
||||
|
||||
return {
|
||||
enableAutoCheckChildren: () => this.enableAutoCheckChildren(),
|
||||
enableAllInputs: () => this.enableAllInputs(),
|
||||
disableAllInputs: () => this.disableAllInputs(),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable automatic check/uncheck of clicked item's children.
|
||||
*/
|
||||
enableAutoCheckChildren() {
|
||||
this.$container.on('change', 'input[type="checkbox"]', (event) => {
|
||||
const $clickedCheckbox = $(event.currentTarget);
|
||||
const $itemWithChildren = $clickedCheckbox.closest('li');
|
||||
|
||||
$itemWithChildren
|
||||
.find('ul input[type="checkbox"]')
|
||||
.prop('checked', $clickedCheckbox.is(':checked'));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Enable all inputs in the choice tree.
|
||||
*/
|
||||
enableAllInputs() {
|
||||
this.$container.find('input').removeAttr('disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Disable all inputs in the choice tree.
|
||||
*/
|
||||
disableAllInputs() {
|
||||
this.$container.find('input').attr('disabled', 'disabled');
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse or expand sub-tree for single parent
|
||||
*
|
||||
* @param {jQuery} $inputWrapper
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleChildTree($inputWrapper) {
|
||||
const $parentWrapper = $inputWrapper.closest('li');
|
||||
|
||||
if ($parentWrapper.hasClass('expanded')) {
|
||||
$parentWrapper
|
||||
.removeClass('expanded')
|
||||
.addClass('collapsed');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($parentWrapper.hasClass('collapsed')) {
|
||||
$parentWrapper
|
||||
.removeClass('collapsed')
|
||||
.addClass('expanded');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collapse or expand whole tree
|
||||
*
|
||||
* @param {jQuery} $action
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleTree($action) {
|
||||
const $parentContainer = $action.closest('.js-choice-tree-container');
|
||||
const action = $action.data('action');
|
||||
|
||||
// toggle action configuration
|
||||
const config = {
|
||||
addClass: {
|
||||
expand: 'expanded',
|
||||
collapse: 'collapsed',
|
||||
},
|
||||
removeClass: {
|
||||
expand: 'collapsed',
|
||||
collapse: 'expanded',
|
||||
},
|
||||
nextAction: {
|
||||
expand: 'collapse',
|
||||
collapse: 'expand',
|
||||
},
|
||||
text: {
|
||||
expand: 'collapsed-text',
|
||||
collapse: 'expanded-text',
|
||||
},
|
||||
icon: {
|
||||
expand: 'collapsed-icon',
|
||||
collapse: 'expanded-icon',
|
||||
},
|
||||
};
|
||||
|
||||
$parentContainer.find('li').each((index, item) => {
|
||||
const $item = $(item);
|
||||
|
||||
if ($item.hasClass(config.removeClass[action])) {
|
||||
$item.removeClass(config.removeClass[action])
|
||||
.addClass(config.addClass[action]);
|
||||
}
|
||||
});
|
||||
|
||||
$action.data('action', config.nextAction[action]);
|
||||
$action.find('.material-icons').text($action.data(config.icon[action]));
|
||||
$action.find('.js-toggle-text').text($action.data(config.text[action]));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,390 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
import _ from 'lodash';
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* This is able to watch an HTML form and parse it as a Javascript object based on a configurable
|
||||
* mapping. Each field from the model is mapped to a form input, or several, each input is watched
|
||||
* to keep the model consistent.
|
||||
*
|
||||
* The model mapping used for this component is an object which uses the modelKey as a key (it represents
|
||||
* the property path in the object, separated by a dot) and the input names as value (they follow Symfony
|
||||
* convention naming using brackets). Here is an example of mapping:
|
||||
*
|
||||
* const modelMapping = {
|
||||
* 'product.stock.quantity': 'product[stock][quantity]',
|
||||
* 'product.price.priceTaxExcluded': [
|
||||
* 'product[price][price_tax_excluded]',
|
||||
* 'product[shortcuts][price][price_tax_excluded]',
|
||||
* ],
|
||||
* };
|
||||
*
|
||||
* As you can see for priceTaxExcluded it is possible to assign multiple inputs to the same modelKey, thus
|
||||
* any update in one of the inputs will update the model, and all these inputs are kept in sync.
|
||||
*
|
||||
* With the previous configuration this component would return an object that looks like this:
|
||||
*
|
||||
* {
|
||||
* product: {
|
||||
* stock: {
|
||||
* // Mapped to product[stock][quantity] input
|
||||
* quantity: 200,
|
||||
* },
|
||||
* price: {
|
||||
* // Mapped to two inputs product[price][price_tax_excluded] and product[shortcuts][price][price_tax_excluded]
|
||||
* priceTaxExcluded: 20.45,
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
export default class FormObjectMapper {
|
||||
/**
|
||||
* @param {jQuery} $form - Form element to attach the mapper to
|
||||
* @param {Object} modelMapping - Structure mapping a model to form names
|
||||
* @param {EventEmitter} eventEmitter
|
||||
* @param {Object} [config] - Event names
|
||||
* @param {Object} [config.updateModel] - Name of the event to listen to trigger a refresh of the model update
|
||||
* @param {Object} [config.modelUpdated] - Name of the event emitted each time the model is updated
|
||||
* @param {Object} [config.modelFieldUpdated] - Name of the event emitted each time a field is updated
|
||||
* @return {Object}
|
||||
*/
|
||||
constructor($form, modelMapping, eventEmitter, config) {
|
||||
this.$form = $form;
|
||||
this.fullModelMapping = modelMapping;
|
||||
this.eventEmitter = eventEmitter;
|
||||
|
||||
const inputConfig = config || {};
|
||||
|
||||
// This event is registered so when it is triggered it forces the form mapping and object update,
|
||||
// it can be useful when some new inputs have been added in the DOM (or removed) so that the model
|
||||
// acknowledges the update
|
||||
this.updateModelEventName = inputConfig.updateModel || 'updateModel';
|
||||
|
||||
// This event is emitted each time the object is updated (from both input change and external event)
|
||||
this.modelUpdatedEventName = inputConfig.modelUpdated || 'modelUpdated';
|
||||
// This event is emitted each time an object field is updated (from both input change and external event)
|
||||
this.modelFieldUpdatedEventName = inputConfig.modelFieldUpdated || 'modelFieldUpdated';
|
||||
|
||||
// Contains callbacks identified by model keys
|
||||
this.watchedProperties = {};
|
||||
|
||||
this.initFormMapping();
|
||||
this.updateFullObject();
|
||||
this.watchUpdates();
|
||||
|
||||
return {
|
||||
/**
|
||||
* Returns the model mapped to the form (current live state)
|
||||
*
|
||||
* @returns {*|{}}
|
||||
*/
|
||||
getModel: () => this.model,
|
||||
|
||||
/**
|
||||
* Returns all inputs associated to a model field.
|
||||
*
|
||||
* @param {string} modelKey
|
||||
*
|
||||
* @returns {undefined|jQuery}
|
||||
*/
|
||||
getInputsFor: (modelKey) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.fullModelMapping, modelKey)) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const inputNames = this.fullModelMapping[modelKey];
|
||||
|
||||
// We must loop manually to keep the order in configuration, if we use jQuery multiple selectors the collection
|
||||
// will be filled respecting the order in the DOM
|
||||
const inputs = [];
|
||||
const domForm = this.$form.get(0);
|
||||
inputNames.forEach((inputName) => {
|
||||
const inputsByName = domForm.querySelectorAll(`[name="${inputName}"]`);
|
||||
|
||||
if (inputsByName.length) {
|
||||
inputsByName.forEach((input) => {
|
||||
inputs.push(input);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return inputs.length ? $(inputs) : undefined;
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a value to a field of the object based on the model key, the object itself is updated
|
||||
* of course but the mapped inputs are also synced (all of them if multiple). Events are also
|
||||
* triggered to indicate the object has been updated (the general and the individual field ones).
|
||||
*
|
||||
* @param {string} modelKey
|
||||
* @param {*|{}} value
|
||||
*/
|
||||
set: (modelKey, value) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.modelMapping, modelKey) || value === this.getValue(modelKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// First update the inputs then the model, so that the event is sent at last
|
||||
this.updateInputValue(modelKey, value);
|
||||
this.updateObjectByKey(modelKey, value);
|
||||
this.eventEmitter.emit(this.modelUpdatedEventName, this.model);
|
||||
},
|
||||
|
||||
/**
|
||||
* Alternative to the event listening, you can watch a specific field of the model and assign a callback.
|
||||
* When the specified model field is updated the event is still thrown but additionally any callback assigned
|
||||
* to this specific value is also called, the parameter is the same event.
|
||||
*
|
||||
* @param {string} modelKey
|
||||
* @param {function} callback
|
||||
*/
|
||||
watch: (modelKey, callback) => {
|
||||
if (!Object.prototype.hasOwnProperty.call(this.watchedProperties, modelKey)) {
|
||||
this.watchedProperties[modelKey] = [];
|
||||
}
|
||||
this.watchedProperties[modelKey].push(callback);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a field from the object based on the model key, you can even get a sub part of the whole model,
|
||||
* this internal method is used by both get and set public methods.
|
||||
*
|
||||
* @param {string} modelKey
|
||||
*
|
||||
* @returns {*|{}|undefined} Returns any element from the model, undefined if not found
|
||||
* @private
|
||||
*/
|
||||
getValue(modelKey) {
|
||||
const modelKeys = modelKey.split('.');
|
||||
|
||||
return $.serializeJSON.deepGet(this.model, modelKeys);
|
||||
}
|
||||
|
||||
/**
|
||||
* Watches if changes happens from the form or via an event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
watchUpdates() {
|
||||
this.$form.on('keyup change dp.change', ':input', _.debounce(
|
||||
(event) => this.inputUpdated(event),
|
||||
350,
|
||||
{maxWait: 1500},
|
||||
));
|
||||
this.eventEmitter.on(this.updateModelEventName, () => this.updateFullObject());
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggered when a form input has been changed.
|
||||
*
|
||||
* @param {jQuery.Event} event
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
inputUpdated(event) {
|
||||
const target = event.currentTarget;
|
||||
|
||||
// All inputs changes are watched, but not all of them are part of the mapping so we ignore them
|
||||
if (!Object.prototype.hasOwnProperty.call(this.formMapping, target.name)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const updatedValue = $(target).val();
|
||||
const updatedModelKey = this.formMapping[target.name];
|
||||
|
||||
// Update the mapped input fields
|
||||
this.updateInputValue(updatedModelKey, updatedValue, target.name);
|
||||
|
||||
// Then update model and emit event
|
||||
this.updateObjectByKey(updatedModelKey, updatedValue);
|
||||
this.eventEmitter.emit(this.modelUpdatedEventName, this.model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update all the inputs mapped to a model key
|
||||
*
|
||||
* @param {string} modelKey
|
||||
* @param {*|{}} value
|
||||
* @param {string|undefined} sourceInputName Source of the change (no need to update it)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateInputValue(modelKey, value, sourceInputName = undefined) {
|
||||
const modelInputs = this.fullModelMapping[modelKey];
|
||||
|
||||
// Update linked inputs (when there is more than one input associated to the model field)
|
||||
if (Array.isArray(modelInputs)) {
|
||||
modelInputs.forEach((inputName) => {
|
||||
if (sourceInputName === inputName) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.updateInputByName(inputName, value);
|
||||
});
|
||||
} else if (sourceInputName !== modelInputs) {
|
||||
this.updateInputByName(modelInputs, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update individual input based on its name
|
||||
*
|
||||
* @param {string} inputName
|
||||
* @param {*|{}} value
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateInputByName(inputName, value) {
|
||||
const $input = $(`[name="${inputName}"]`, this.$form);
|
||||
|
||||
if (!$input.length) {
|
||||
console.error(`Input with name ${inputName} is not present in form.`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// This check is important to avoid infinite loops, we don't use strict equality on purpose because it would result
|
||||
// into a potential infinite loop if type don't match, which can easily happen with a number value and a text input.
|
||||
// eslint-disable-next-line eqeqeq
|
||||
if ($input.val() != value) {
|
||||
$input.val(value);
|
||||
|
||||
if ($input.data('toggle') === 'select2') {
|
||||
// This is required for select2, because only changing the val doesn't update the wrapping component
|
||||
$input.trigger('change');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Serializes and updates the object based on form content and the mapping configuration, finally
|
||||
* emit an event for external components that may need the update.
|
||||
*
|
||||
* This method is called when this component initializes or when triggered by an external event.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateFullObject() {
|
||||
const serializedForm = this.$form.serializeJSON();
|
||||
this.model = {};
|
||||
Object.keys(this.modelMapping).forEach((modelKey) => {
|
||||
const formMapping = this.modelMapping[modelKey];
|
||||
const formKeys = $.serializeJSON.splitInputNameIntoKeysArray(formMapping);
|
||||
const formValue = $.serializeJSON.deepGet(serializedForm, formKeys);
|
||||
|
||||
this.updateObjectByKey(modelKey, formValue);
|
||||
});
|
||||
|
||||
this.eventEmitter.emit(this.modelUpdatedEventName, this.model);
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a specific field of the object.
|
||||
*
|
||||
* @param {string} modelKey
|
||||
* @param {*|{}} value
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
updateObjectByKey(modelKey, value) {
|
||||
const modelKeys = modelKey.split('.');
|
||||
const previousValue = $.serializeJSON.deepGet(this.model, modelKeys);
|
||||
|
||||
// This check has two interests, there is no point in modifying a value or emit an event for a value that did not
|
||||
// change, and it avoids infinite loops when the object field are co-dependent and need to be updated dynamically
|
||||
// (ex: update price tax included when price tax excluded is updated and vice versa, without this check an infinite
|
||||
// loop would happen)
|
||||
if (previousValue === value) {
|
||||
return;
|
||||
}
|
||||
|
||||
$.serializeJSON.deepSet(this.model, modelKeys, value);
|
||||
|
||||
const updateEvent = {
|
||||
object: this.model,
|
||||
modelKey,
|
||||
value,
|
||||
previousValue,
|
||||
};
|
||||
this.eventEmitter.emit(this.modelFieldUpdatedEventName, updateEvent);
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(this.watchedProperties, modelKey)) {
|
||||
const propertyWatchers = this.watchedProperties[modelKey];
|
||||
propertyWatchers.forEach((callback) => {
|
||||
callback(updateEvent);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reverse the initial mapping Model->Form to the opposite Form->Model
|
||||
* This simplifies the sync in when data updates.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
initFormMapping() {
|
||||
// modelMapping is a light version of the fullModelMapping, it only contains one input name which is considered
|
||||
// as the default one (when full object is updated, only the default input is used)
|
||||
this.modelMapping = {};
|
||||
|
||||
// formMapping is the inverse of modelMapping for each input name it associated the model key, it is generated for
|
||||
// performance and convenience, this allows to get mapping data faster in other functions
|
||||
this.formMapping = {};
|
||||
|
||||
Object.keys(this.fullModelMapping).forEach((modelKey) => {
|
||||
const formMapping = this.fullModelMapping[modelKey];
|
||||
|
||||
if (Array.isArray(formMapping)) {
|
||||
formMapping.forEach((aliasFormMapping) => {
|
||||
this.addFormMapping(aliasFormMapping, modelKey);
|
||||
});
|
||||
} else {
|
||||
this.addFormMapping(formMapping, modelKey);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} formName
|
||||
* @param {string} modelMapping
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
addFormMapping(formName, modelMapping) {
|
||||
if (Object.prototype.hasOwnProperty.call(this.formMapping, formName)) {
|
||||
console.error(`The form element ${formName} is already mapped to ${this.formMapping[formName]}`);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
this.formMapping[formName] = modelMapping;
|
||||
this.modelMapping[modelMapping] = formName;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Component responsible for displaying form popover errors with modified width which is calculated based on the
|
||||
* form group width.
|
||||
*/
|
||||
$(() => {
|
||||
// loads form popover instance
|
||||
$('[data-toggle="form-popover-error"]').popover({
|
||||
html: true,
|
||||
content() {
|
||||
return getErrorContent(this);
|
||||
},
|
||||
});
|
||||
|
||||
/**
|
||||
* Recalculates popover position so it is always aligned horizontally and width is identical
|
||||
* to the child elements of the form.
|
||||
* @param {Object} event
|
||||
*/
|
||||
const repositionPopover = (event) => {
|
||||
const $element = $(event.currentTarget);
|
||||
const $formGroup = $element.closest('.form-group');
|
||||
const $invalidFeedbackContainer = $formGroup.find('.invalid-feedback-container');
|
||||
const $errorPopover = $formGroup.find('.form-popover-error');
|
||||
|
||||
const localeVisibleElementWidth = $invalidFeedbackContainer.width();
|
||||
|
||||
$errorPopover.css('width', localeVisibleElementWidth);
|
||||
|
||||
const horizontalDifference = getHorizontalDifference($invalidFeedbackContainer, $errorPopover);
|
||||
|
||||
$errorPopover.css('left', `${horizontalDifference}px`);
|
||||
};
|
||||
|
||||
/**
|
||||
* gets horizontal difference which helps to align popover horizontally.
|
||||
* @param {jQuery} $invalidFeedbackContainer
|
||||
* @param {jQuery} $errorPopover
|
||||
* @returns {number}
|
||||
*/
|
||||
const getHorizontalDifference = ($invalidFeedbackContainer, $errorPopover) => {
|
||||
const inputHorizontalPosition = $invalidFeedbackContainer.offset().left;
|
||||
const popoverHorizontalPosition = $errorPopover.offset().left;
|
||||
|
||||
return inputHorizontalPosition - popoverHorizontalPosition;
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets popover error content pre-fetched in html. It used unique selector to identify which one content to render.
|
||||
*
|
||||
* @param popoverTriggerElement
|
||||
* @returns {jQuery}
|
||||
*/
|
||||
const getErrorContent = (popoverTriggerElement) => {
|
||||
const popoverTriggerId = $(popoverTriggerElement).data('id');
|
||||
|
||||
return $(`.js-popover-error-content[data-id="${popoverTriggerId}"]`).html();
|
||||
};
|
||||
|
||||
// registers the event which displays the popover
|
||||
$(document).on('shown.bs.popover', '[data-toggle="form-popover-error"]', (event) => repositionPopover(event));
|
||||
});
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
export default class MultistoreConfigField {
|
||||
constructor() {
|
||||
this.updateMultistoreFieldOnChange();
|
||||
}
|
||||
|
||||
updateMultistoreFieldOnChange() {
|
||||
$(document).on('change', '.multistore-checkbox', function () {
|
||||
const input = $(this).closest('.form-group').find(':input:not(.multistore-checkbox)');
|
||||
const inputContainer = $(this).closest('.form-group').find('.input-container');
|
||||
const labelContainer = $(this).closest('.form-group').find('.form-control-label');
|
||||
const isChecked = $(this).is(':checked');
|
||||
inputContainer.toggleClass('disabled', !isChecked);
|
||||
labelContainer.toggleClass('disabled', !isChecked);
|
||||
input.prop('disabled', !isChecked);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Responsible for opening another page with specified url.
|
||||
* For example used in 'Save and preview' cms page create/edit actions.
|
||||
*
|
||||
* Usage: In selector element attr 'data-preview-url' provide page url.
|
||||
* The page will be opened once provided 'open_preview' parameter in query url
|
||||
*/
|
||||
export default class PreviewOpener {
|
||||
constructor(previewUrlSelector) {
|
||||
this.previewUrl = $(previewUrlSelector).data('preview-url');
|
||||
this.open();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Opens new page of provided url
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
open() {
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
|
||||
if (this.previewUrl && urlParams.has('open_preview')) {
|
||||
window.open(this.previewUrl, '_blank');
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
import {showGrowl} from '@app/utils/growl';
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Activates, deactivates, shows, hides submit button inside an input
|
||||
* (depending if input was changed comparing to initial value)
|
||||
* After button is clicked, component fires the callback function which was provided to constructor.
|
||||
*/
|
||||
export default class SubmittableInput {
|
||||
/**
|
||||
* @param {String} wrapperSelector
|
||||
* @param {Function} callback
|
||||
*
|
||||
* @returns {{}}
|
||||
*/
|
||||
constructor(wrapperSelector, callback) {
|
||||
this.inputSelector = '.submittable-input';
|
||||
this.callback = callback;
|
||||
this.wrapperSelector = wrapperSelector;
|
||||
this.buttonSelector = '.check-button';
|
||||
|
||||
this.init();
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
init() {
|
||||
const inputs = `${this.wrapperSelector} ${this.inputSelector}`;
|
||||
const that = this;
|
||||
|
||||
$(document).on('focus', inputs, (e) => {
|
||||
this.refreshButtonState(e.currentTarget, true);
|
||||
});
|
||||
$(document).on('input blur', inputs, (e) => {
|
||||
this.refreshButtonState(e.currentTarget);
|
||||
});
|
||||
$(document).on(
|
||||
'click',
|
||||
`${this.wrapperSelector} ${this.buttonSelector}`,
|
||||
function () {
|
||||
that.submitInput(this);
|
||||
},
|
||||
);
|
||||
$(document).on('keyup', inputs, (e) => {
|
||||
if (e.keyCode === 13) {
|
||||
e.preventDefault();
|
||||
const button = this.findButton(e.target);
|
||||
|
||||
this.submitInput(button);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
submitInput(button) {
|
||||
const input = this.findInput(button);
|
||||
|
||||
this.toggleLoading(button, true);
|
||||
|
||||
this.callback(input)
|
||||
.then((response) => {
|
||||
$(input).data('initial-value', input.value);
|
||||
this.toggleButtonVisibility(button, false);
|
||||
|
||||
if (response.message) {
|
||||
showGrowl('success', response.message);
|
||||
}
|
||||
this.toggleLoading(button, false);
|
||||
})
|
||||
.catch((error) => {
|
||||
this.toggleError(button, true);
|
||||
this.toggleButtonVisibility(button, false);
|
||||
this.toggleLoading(button, false);
|
||||
|
||||
if (typeof error.responseJSON.errors === 'undefined') {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = error.responseJSON.errors;
|
||||
Object.keys(messages).forEach((key) => {
|
||||
showGrowl('error', messages[key]);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} input
|
||||
* @param {Boolean|null} visible
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
refreshButtonState(input, visible = null) {
|
||||
const button = this.findButton(input);
|
||||
const valueWasChanged = this.inputValueChanged(input);
|
||||
this.toggleButtonActivity(button, valueWasChanged);
|
||||
|
||||
if (visible !== null) {
|
||||
this.toggleButtonVisibility(button, visible);
|
||||
} else {
|
||||
this.toggleButtonVisibility(button, valueWasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} button
|
||||
* @param {Boolean} active
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleButtonActivity(button, active) {
|
||||
$(button).toggleClass('active', active);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} button
|
||||
* @param {Boolean} visible
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleButtonVisibility(button, visible) {
|
||||
$(button).toggleClass('d-none', !visible);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} button
|
||||
* @param {Boolean} visible
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleLoading(button, loading) {
|
||||
if (loading) {
|
||||
$(button).html('<span class="spinner-border spinner-border-sm"></span>');
|
||||
} else {
|
||||
$(button).html('<i class="material-icons">check</i>');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} button
|
||||
* @param {Boolean} visible
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
toggleError(button, error) {
|
||||
const input = this.findInput(button);
|
||||
|
||||
$(input).toggleClass('is-invalid', error);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} input
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
findButton(input) {
|
||||
return $(input)
|
||||
.closest(this.wrapperSelector)
|
||||
.find(this.buttonSelector)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} domElement
|
||||
*
|
||||
* @returns {HTMLElement}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
findInput(domElement) {
|
||||
return $(domElement)
|
||||
.closest(this.wrapperSelector)
|
||||
.find(this.inputSelector)[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {HTMLElement} input
|
||||
*
|
||||
* @returns {Boolean}
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
inputValueChanged(input) {
|
||||
const initialValue = $(input).data('initial-value');
|
||||
let newValue = $(input).val();
|
||||
|
||||
if ($(input).hasClass('is-invalid')) {
|
||||
$(input).removeClass('is-invalid');
|
||||
}
|
||||
|
||||
if (typeof initialValue === 'number') {
|
||||
newValue = Number(newValue);
|
||||
}
|
||||
|
||||
return initialValue !== newValue;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* TextWithLengthCounter handles input with length counter UI.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* There must be an element that wraps both input & counter display with ".js-text-with-length-counter" class.
|
||||
* Counter display must have ".js-countable-text-display" class and input must have ".js-countable-text-input" class.
|
||||
* Text input must have "data-max-length" attribute.
|
||||
*
|
||||
* <div class="js-text-with-length-counter">
|
||||
* <span class="js-countable-text"></span>
|
||||
* <input class="js-countable-input" data-max-length="255">
|
||||
* </div>
|
||||
*
|
||||
* In Javascript you must enable this component:
|
||||
*
|
||||
* new TextWithLengthCounter();
|
||||
*/
|
||||
export default class TextWithLengthCounter {
|
||||
constructor() {
|
||||
this.wrapperSelector = '.js-text-with-length-counter';
|
||||
this.textSelector = '.js-countable-text';
|
||||
this.inputSelector = '.js-countable-input';
|
||||
|
||||
$(document).on('input', `${this.wrapperSelector} ${this.inputSelector}`, (e) => {
|
||||
const $input = $(e.currentTarget);
|
||||
const remainingLength = $input.data('max-length') - $input.val().length;
|
||||
|
||||
$input.closest(this.wrapperSelector).find(this.textSelector).text(remainingLength);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* This component is implemented to work with TextWithRecommendedLengthType,
|
||||
* but can be used as standalone component as well.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* Define your HTML with input and counter. Example:
|
||||
*
|
||||
* <input id="myInput"
|
||||
* class="js-recommended-length-input"
|
||||
* data-recommended-length-counter="#myInput_recommended_length_counter"
|
||||
* >
|
||||
*
|
||||
* <div id"myInput_recommended_length_counter">
|
||||
* <span class="js-current-length">0</span> of 70 characters used (recommended)
|
||||
* </div>
|
||||
*
|
||||
* NOTE: You must use exactly the same Classes, but IDs can be different!
|
||||
*
|
||||
* Then enable component in JavaScript:
|
||||
*
|
||||
* new TextWithRecommendedLengthCounter();
|
||||
*/
|
||||
export default class TextWithRecommendedLengthCounter {
|
||||
constructor() {
|
||||
$(document).on('input', '.js-recommended-length-input', (event) => {
|
||||
const $input = $(event.currentTarget);
|
||||
|
||||
$($input.data('recommended-length-counter')).find('.js-current-length').text($input.val().length);
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright since 2007 PrestaShop SA and Contributors
|
||||
* PrestaShop is an International Registered Trademark & Property of PrestaShop SA
|
||||
*
|
||||
* NOTICE OF LICENSE
|
||||
*
|
||||
* This source file is subject to the Open Software License (OSL 3.0)
|
||||
* that is bundled with this package in the file LICENSE.md.
|
||||
* It is also available through the world-wide-web at this URL:
|
||||
* https://opensource.org/licenses/OSL-3.0
|
||||
* If you did not receive a copy of the license and are unable to
|
||||
* obtain it through the world-wide-web, please send an email
|
||||
* to license@prestashop.com so we can send you a copy immediately.
|
||||
*
|
||||
* DISCLAIMER
|
||||
*
|
||||
* Do not edit or add to this file if you wish to upgrade PrestaShop to newer
|
||||
* versions in the future. If you wish to customize PrestaShop for your
|
||||
* needs please refer to https://devdocs.prestashop.com/ for more information.
|
||||
*
|
||||
* @author PrestaShop SA and Contributors <contact@prestashop.com>
|
||||
* @copyright Since 2007 PrestaShop SA and Contributors
|
||||
* @license https://opensource.org/licenses/OSL-3.0 Open Software License (OSL 3.0)
|
||||
*/
|
||||
|
||||
const {$} = window;
|
||||
|
||||
/**
|
||||
* Component responsible for filtering select values by language selected.
|
||||
*/
|
||||
export default class TranslatableChoice {
|
||||
constructor() {
|
||||
// registers the event which displays the popover
|
||||
$(document).on('change', 'select.translatable_choice_language', (event) => {
|
||||
this.filterSelect(event);
|
||||
});
|
||||
|
||||
$('select.translatable_choice_language').trigger('change');
|
||||
}
|
||||
|
||||
filterSelect(event) {
|
||||
const $element = $(event.currentTarget);
|
||||
const $formGroup = $element.closest('.form-group');
|
||||
const language = $element.find('option:selected').val();
|
||||
|
||||
// show all the languages selects
|
||||
$formGroup.find(`select.translatable_choice[data-language="${language}"]`).parent().show();
|
||||
|
||||
const $selects = $formGroup.find('select.translatable_choice');
|
||||
|
||||
// Hide all the selects not corresponding to the language selected
|
||||
$selects.not(`select.translatable_choice[data-language="${language}"]`).each((index, item) => {
|
||||
$(item).parent().hide();
|
||||
});
|
||||
|
||||
// Bind choice selection to fill the hidden input
|
||||
this.bindValueSelection($selects);
|
||||
}
|
||||
|
||||
bindValueSelection($selects) {
|
||||
$selects.each((index, element) => {
|
||||
$(element).on('change', (event) => {
|
||||
const $select = $(event.currentTarget);
|
||||
const selectId = $select.attr('id');
|
||||
$(`#${selectId}_value`).val($select.find('option:selected').val());
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user