| Server IP : 85.158.181.41 / Your IP : 216.73.217.12 Web Server : Apache System : Linux cloud9-vm129 6.1.178+1-ph #ph SMP PREEMPT_DYNAMIC Wed Jul 29 09:00:54 UTC 2026 x86_64 User : moncbefd ( 1024) PHP Version : 7.3.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : OFF | Pkexec : OFF Directory : /home/moncbefd/www.moneta.at/themes/Honeygrid/javascripts/source/widgets/ |
Upload File : |
/* --------------------------------------------------------------
product_cart_handler.js 2019-05-23
Gambio GmbH
http://www.gambio.de
Copyright (c) 2018 Gambio GmbH
Released under the GNU General Public License (Version 2)
[http://www.gnu.org/licenses/gpl-2.0.html]
--------------------------------------------------------------
*/
/**
* Component that includes the functionality for
* the add-to-cart, refresh and delete buttons
* on the wishlist and cart
*/
gambio.widgets.module(
'product_cart_handler',
[
'form',
'xhr',
gambio.source + '/libs/events',
gambio.source + '/libs/modal.ext-magnific',
gambio.source + '/libs/modal'
],
function (data) {
'use strict';
// ########## VARIABLE INITIALIZATION ##########
var $this = $(this),
$window = $(window),
$body = $('body'),
$form = null,
$updateTarget = null,
$deleteField = null,
$cartEmpty = null,
$cartNotEmpty = null,
deleteFieldName = null,
action = null,
busy = null,
updateList = false,
transition = null,
active = {},
isChanged = false,
defaults = {
// Use an AJAX to update the form
ajax: true,
// Show an confirm-layer on deletion of an item
confirmDelete: false,
// Selector of the hidden field for the deletion entries
deleteInput: '#field_cart_delete_products_id',
// Trigger an event to that item on an successfull ajax (e.g. the shipping costs element)
updateTarget: '.shipping-calculation',
// The URL for the quantity check of the item
checkUrl: 'shop.php?do=CheckQuantity',
// If an URL is set, this one will be requests for status updates on tab focus
updateUrl: 'shop.php?do=Cart',
changeClass: 'has-changed', // Class that gets added if an input has changed
errorClass: 'error', // Class that gets added to the row if an error has occured
cartEmpty: '.cart-empty', // Show this selection if the cart is empty or hide it else
cartNotEmpty: '.cart-not-empty', // Show this selection if the cart is not empty or hide it else
classLoading: 'loading', // The class that gets added to an currently updating row
actions: { // The actions that getting appended to the submit url on the different type of updates
add: 'wishlist_to_cart',
delete: 'update_product',
refresh: 'update_wishlist'
},
ajaxActions: { // URLs for the ajax updates on the different actions
add: 'shop.php?do=WishList/AddToCart',
delete: 'shop.php?do=Cart/Delete',
refresh: 'shop.php?do=Cart/Update'
},
selectorMapping: {
buttons: '.shopping-cart-button',
giftContent: '.gift-cart-content-wrapper',
giftLayer: '.gift-cart-layer',
shareContent: '.share-cart-content-wrapper',
shareLayer: '.share-cart-layer',
hiddenOptions: '#cart_quantity .hidden-options',
message: '.global-error-messages',
infoMessage: '.info-message',
shippingInformation: '#shipping-information-layer',
totals: '#cart_quantity .total-box',
errorMsg: '.error-msg',
submit: '.button-submit'
}
},
options = $.extend(false, {}, defaults, data),
module = {};
// ########## HELPER FUNCTIONS ##########
/**
* Updates the form action to the type given
* in the options.actions object
* @param {string} type The action name
* @private
*/
var _setAction = function (type) {
if (options.ajax) {
action = options.ajaxActions[type];
} else if (options.actions && options.actions[type]) {
action = action.replace(/(action=)[^\&]+/, '$1' + options.actions[type]);
$form.attr('action', action);
}
};
/**
* Helper function that updates the
* hidden data attributes with the current
* values of the input fields
* @param {object} $target jQuery selection of the topmost container
* @private
*/
var _updateDataValues = function ($target) {
$target
.find('input[type="text"]')
.each(function () {
var $self = $(this),
value = $self.val();
$self.data('oldValue', value);
});
};
/**
* Helper function that restores the values
* stored by the _updateDataValues function
* @param {object} dataset The data object of all targets that needs to be reset
* @private
*/
var _restoreDataValues = function (dataset) {
// Reset each changed field given
// by the dataset target
$.each(dataset, function () {
var value = this;
value
.target
.find('.' + options.changeClass)
.each(function () {
var $self = $(this),
name = $self.attr('name').replace('[]', ''),
val = $self.data().oldValue;
value[name][0] = val;
$self
.val(val)
.removeClass(options.changeClass);
});
});
};
/**
* Helper function that generates an array of datasets from the form. Each array item
* contains the data of one row (inclusive the attributes data from the form head belonging
* to the row). Additionally it adds the target-parameter to each dataset which contains
* the selection of the row,the current dataset belongs to.
*
* @param {object} $row The optional row selection the data gets from. If no selection is given, the form
* gets selected.
* @return {Array} The array with the datasets of each row
*
* @private
*/
var _generateFormdataObject = function ($row) {
var $target = ($row && $row.length) ? $row : $form,
$rows = ($row && $row.length) ? $row : $form.find('.order-wishlist .item:gt(0)'),
$hiddens = $form.find('.hidden-options input[type="hidden"]'),
dataset = jse.libs.form.getData($target),
result = [],
tmpResult = null;
$.each(dataset.products_id, function (i, v) {
tmpResult = {};
tmpResult.target = $rows.eq(i);
// Store the data from the current row as a json
$.each(dataset, function (key, value) {
if (typeof value === 'object' && value[i] !== undefined) {
// Store the value as an array to be compliant with the old API
tmpResult[key] = [value[i]];
}
});
// Get the hidden fields for the attributes
// belonging to this row from the form head
$hiddens
.filter('[name^="id[' + v + '"], .force')
.each(function () {
var $self = $(this),
name = $self.attr('name');
tmpResult[name] = $self.val();
});
// Push the generated json to the final result array
result.push(tmpResult);
});
return result;
};
/**
* Function that checks the form / the row if the combination
* and quantity is valid. It returns an promise which gets rejected
* if in the scope was an invalid value. In other cases it gets
* resolved. If it is detecting changes inside the form it can
* show an info layer to the user and / or revert the changes
* (depending on the caller parameters)
* @param {boolean} showChanges Show an info-layer if changes would be refused
* @param {boolean} revertChanges Resets the form values with the one from the data attributes if true
* @param {object} formdata Json that contains the data to check
* @return {*} Returns a promise
* @private
*/
var _checkForm = function (showChanges, revertChanges, formdata) {
var promises = [],
hasChanged = false;
// Get the complete form data if no row data is given
formdata = formdata || _generateFormdataObject();
// Check the formdata for changed values
$.each(formdata, function () {
var $changed = this.target.find('.' + options.changeClass);
hasChanged = hasChanged || !!$changed.length;
return !hasChanged;
});
return $.when.apply(undefined, promises).promise();
};
/**
* Helper function that cleans up the process state
* (Needed especially after ajax requests, to be able
* to make further requests)
* @param {string} id The product id that needs to be reseted
* @return {Array.<T>} Returns an array without empty fields
* @private
*/
var _cleanupArray = function (id, $row) {
delete active['product_' + id];
$row.removeClass('loading');
return active;
};
/**
* Helper function that does the general form update
* after an ajax request
* @param {object} $target The jQuery selection of the target elements.
* @param {object} result The result of the ajax request.
* @param {string} type The executed action type.
* @private
*/
var _updateForm = function ($target, result, type) {
// Update the rest of the page
jse.libs.theme.helpers.fill(result.content, $body, options.selectorMapping);
// Toggle info-messages visibility.
$('.info-message').toggleClass('hidden', $('.info-message').text() === '');
// Inform other widgets about the update
$updateTarget.trigger(jse.libs.theme.events.CART_UPDATED(), []);
$body.trigger(jse.libs.theme.events.CART_UPDATE(), (type === 'add'));
// Update the hidden data attributes of that row
_updateDataValues($target);
if ($.isEmptyObject(result.products)) {
// Hide the table if no products are at the list
$cartNotEmpty.addClass('hidden');
$cartEmpty.removeClass('hidden');
} else {
// Show the table if there are products at it
$cartEmpty.addClass('hidden');
$cartNotEmpty.removeClass('hidden');
}
// reinitialize widgets in updated DOM
window.gambio.widgets.init($this);
};
/**
* Helper function to update product specific messages.
* Therefor it calls AJAX-requests (in case ajax is
* enabled) to the server to get the updated information
* about the table state. If ajax isn't enabled, it simply
* submits the form.
* @param {object} messages Object with articleId => message configuration object
* @private
*/
var _updateArticlesMessage = function (messages) {
//update all messages og the request
$.each(messages, function (product, message) {
let messageCfg = {'message': message};
//let productId = product.match(/\d+/)[0];
// Find the related product tr
let item = $('input[value^="' + product + '"]').closest('tr');
jse.libs.theme.helpers.fill(messageCfg, item, options.selectorMapping);
if (item.find('.error-msg').text() !== '') {
item.find('.error-msg').show();
}
});
}
/**
* Helper function that processes the list updates.
* Therefor it calls AJAX-requests (in case ajax is
* enabled) to the server to get the updated information
* about the table state. If ajax isn't enabled, it simply
* submits the form.
* @param {object} $target The jQuery selection of the row that gets updated
* @param {object} dataset The data collected from the target row in JSON format
* @param {article} article The products id of the article in that row
* @param {article} type The operation type can either be "add", "delete" or "refresh".
* @private
*/
var _executeAction = function ($row, $target, dataset, article, type) {
if (options.ajax) {
// Delete the target element because ajax requests
// will fail with a jQuery selection in the data json
delete dataset.target;
$row.trigger(jse.libs.theme.events.TRANSITION(), transition);
// Perform an ajax if the data is valid and the options for ajax is set
jse.libs.xhr.ajax({url: action, data: dataset}, true).done(function (result) {
// Perform hooks
jse.libs.hooks.execute(jse.libs.hooks.keys.shop.cart.change, {
$target,
dataset,
article,
type,
result,
}, 500);
// Update the product row
var $markup = $(result.products['product_' + article] || '');
// Toggle error-messages visibility.
$markup.removeClass(options.classLoading);
$target.replaceWith($markup);
_updateArticlesMessage(result.content.errorMessageList);
delete result.content.errorMessageList;
_updateForm($target, result, type);
var productNumber = article.match(/\d+/)[0];
// Find all items with the same product number
var $items = $('input[value^="' + productNumber + '"]').parent('td');
// Apply the new markup foreach item which has the same product number.
$items.each(function () {
if (!$(this).find('input[value="' + article + '"]').length) {
var number = $(this).find('input[id="products_id[]"]').attr('value');
$markup = $(result.products['product_' + number] || '');
$target = $(this).parent('tr');
$target.replaceWith($markup);
}
});
}).always(function () {
_cleanupArray(article, $row);
});
} else {
// Cleanup the active array on fail / success
// of the following submit. This is a fallback
// if an other component would prevent the submit
// in some cases, so that this script can perform
// actions again
var deferred = $.Deferred();
deferred.always(function () {
_cleanupArray(article, $row);
});
// Submit the form
$form.trigger('submit', deferred);
}
};
// ########## EVENT HANDLER ##########
/**
* Adds an class to the changed input
* field, so that it's styling shows
* that it wasn't refreshed till now
* @private
*/
var _inputHandler = function () {
var $self = $(this),
value = $self.val(),
oldValue = $self.data().oldValue,
hasNewValue = value !== oldValue;
if (hasNewValue) {
isChanged = hasNewValue;
$self.addClass(options.changeClass);
} else {
$self.removeClass(options.changeClass);
}
_updateChangeState();
};
/**
* Handle the blur event
* @private
*/
var _blurHandler = function () {
var $self = $(this),
value = $self.val(),
oldValue = $self.data().oldValue,
hasNewValue = value !== oldValue;
if (hasNewValue) {
$self
.closest('.item')
.find('.button-refresh')
.first()
.trigger('click');
}
};
/**
* Handler that listens on click events on the
* buttons "refresh", "delete" & "add to cart".
* It validates the form / row and passes the
* the data to an submit execute funciton if valid
* @param {object} e jQuery event object
* @private
*/
var _clickHandler = function (e) {
e.preventDefault();
e.stopPropagation();
var $self = $(this),
$row = $self.closest('.item'),
type = e.data.type,
rowdata = _generateFormdataObject($row)[0],
article = rowdata.products_id[0],
$target = rowdata.target,
title = $target.find('.product-title').text();
// Add loading class
$row.addClass('loading');
// Check if there is no current process for this article
// or in case it's no ajax call there is NO other process
if ($.isEmptyObject(active) || (options.ajax && !active['product_' + article])) {
active['product_' + article] = true;
_setAction(type);
switch (type) {
case 'delete':
// Update the form and the dataset with
// the article id to delete
$deleteField.val(article);
rowdata[deleteFieldName] = [article];
if (options.confirmDelete) {
// Open a modal layer to confirm the deletion
var modalTitle = jse.core.lang.translate('CART_WISHLIST_DELETE_TITLE', 'general'),
modalMessage = jse.core.lang.translate('CART_WISHLIST_DELETE', 'general');
jse.libs.theme.modal.confirm({
content: modalMessage,
title: modalTitle
}).done(function () {
var deferred = $.Deferred();
deferred.done(function () {
_executeAction($row, $target, rowdata, article, type);
});
$body.trigger(jse.libs.theme.events.WISHLIST_CART_DELETE(), [
{
'deferred': deferred,
'dataset': rowdata
}
]);
}).fail(function () {
_cleanupArray(article, $row);
});
} else {
var deferred = $.Deferred();
deferred.done(function () {
_executeAction($row, $target, rowdata, article, type);
});
$body.trigger(jse.libs.theme.events.WISHLIST_CART_DELETE(), [
{
'deferred': deferred,
'dataset': rowdata
}
]);
}
break;
default:
// In all other cases check if the form
// has valid values and continue with the
// done callback if valid
_checkForm(false, false, [$.extend(true, {}, rowdata)])
.done(function () {
// Empty the delete hidden field in case it was set before
$deleteField.val('');
var event = null;
if (type === 'add') {
event = jse.libs.theme.events.WISHLIST_TO_CART();
}
if (event) {
var deferred = $.Deferred();
deferred.done(function () {
_executeAction($row, $target, rowdata, article, type);
});
$body.trigger(event, [{'deferred': deferred, 'dataset': rowdata}]);
} else {
_executeAction($row, $target, rowdata, article, type);
}
}).fail(function () {
_cleanupArray(article, $row);
});
break;
}
}
};
/**
* Prevent the submit event that was triggerd
* by user or by script. If it was triggered
* by the user, check if it was an "Enter"-key
* submit from an input field. If so, execute
* the refresh functionality for that row.
* If the event was triggered by the script
* (identified by the data flag "d") check the
* whole form for errors. Only in case of valid
* data proceed the submit
* @param {object} e jQuery event object
* @param {boolean} d A flag that identifies that the submit was triggered by this script
* @private
*/
var _submitHandler = function (e, d) {
// Prevent the default behaviour
// in both cases
e.preventDefault();
e.stopPropagation();
if (!d && e.originalEvent) {
// Check if an input field has triggerd the submit event
// and call the refresh handler
var $source = $(e.originalEvent.explicitOriginalTarget);
if ($source.length && $source.is('input[type="text"]')) {
$source
.closest('.item')
.find('.button-refresh')
.first()
.trigger('click');
}
} else if (d) {
// Check the whole form and only submit
// it if it's valid
_checkForm().done(function () {
// Remove the submit event handler
// on a successful validation and
// trigger a submit again, so that the
// browser executes it's default behavior
$form
.off('submit')
.trigger('submit');
// Resolve the deferred if given
if (typeof d === 'object') {
d.resolve();
}
}).fail(function () {
// Reject the deferred if given
if (typeof d === 'object') {
d.reject();
}
});
}
};
/**
* Event handler for clicking on the proceed
* button to get to the checkout process. It
* checks all items again if they contain valid
* data. Only if so, proceed
* @param {object} e jQuery event object
* @private
*/
var _submitButtonHandler = function (e) {
e.preventDefault();
e.stopPropagation();
if (isChanged) {
// Get the complete form data if no row data is given
var formdata = _generateFormdataObject();
// Check the formdata for changed values
$.each(formdata, function () {
var $changedInput = this.target.find('.' + options.changeClass);
if ($changedInput) {
$changedInput
.closest('.item')
.find('.button-refresh')
.first()
.trigger('click');
}
});
isChanged = false;
_updateChangeState();
return;
}
var $self = $(this),
destination = $self.attr('href');
// Check if there is any other process running
if ($.isEmptyObject(active) && !busy && !updateList) {
busy = true;
_checkForm(true, true).done(function () {
function callback() {
location.href = destination;
}
jse.libs.hooks.execute(jse.libs.hooks.keys.shop.cart.checkout, {event: e}, 500)
.then(callback)
.catch(callback);
}).always(function () {
busy = false;
});
}
};
/**
* Event handler that checks the form and
* resolves or rejects the delivered deferred
* (Used for external payment modules to
* check if the form is valid)
* @param {object} e jQuery event object
* @param {object} d JSON object with the event settings
* @private
*/
var _checkFormHandler = function (e, d) {
e.stopPropagation();
d = d || {};
_checkForm(d.showChanges, d.revertChanges).done(function () {
if (d.deferred) {
d.deferred.resolve();
}
}).fail(function () {
if (d.deferred) {
d.deferred.reject();
}
});
};
/**
* Function that updates the list on focus of
* the window
* @private
*/
var _updateList = function () {
updateList = true;
jse.libs.xhr.ajax({url: options.updateUrl}, true).done(function (result) {
// Init with he first line since this ist the heading
var $lastScanned = $form.find('.order-wishlist .item').first(),
$target = $();
// Iterate through the products object and search for the
// products inside the markup. If the product was found,
// update the values, if not add the product row at the
// correct position
$.each(result.products, function (key, value) {
var articleId = key.replace('product_', ''),
$article = $form.find('input[name="products_id[]"][value="' + articleId + '"]'),
$row = null;
if (!$article.length) {
// The article wasn't found on page
// -> add it
$row = $(value);
$row.insertAfter($lastScanned);
} else {
// The article was found on page
// -> update it
$row = $article.closest('.item');
var $qty = $row.find('input[name="cart_quantity[]"]'),
oldQty = parseFloat($qty.data().oldValue),
currentQty = parseFloat($qty.val()),
newQty = parseFloat($(value).find('input[name="cart_quantity[]"]').val());
$qty.data('oldValue', newQty);
// Add or remove the changed classes depending on
// the quantity changes and the on page stored values
if (oldQty === currentQty && currentQty !== newQty) {
$qty.addClass(options.changeClass);
} else if (oldQty !== currentQty && currentQty === newQty) {
$qty.removeClass(options.changeClass);
}
}
$target.add($row);
$lastScanned = $row;
});
// Update the rest of the form
_updateForm($target, result);
}).always(function () {
updateList = false;
});
};
/**
* Update the input change state
* @private
*/
var _updateChangeState = function () {
$form
.find(options.selectorMapping.submit)
.text(jse.core.lang.translate(isChanged ? 'refresh' : 'checkout', 'buttons'))
};
// ########## INITIALIZATION ##########
/**
* Init function of the widget
* @constructor
*/
module.init = function (done) {
$updateTarget = $(options.updateTarget);
$cartEmpty = $(options.cartEmpty);
$cartNotEmpty = $(options.cartNotEmpty);
$deleteField = $(options.deleteInput);
$form = $this.find('form').first();
deleteFieldName = $deleteField.attr('name');
action = $form.attr('action');
transition = {open: true, classOpen: options.classLoading};
// Sets the current value of the input
// to an hidden data attribute
_updateDataValues($form);
$form
.on('input', 'input[type="text"]:not(.gift-coupon-code-input)', _inputHandler)
.on('blur', 'input[type="text"]:not(.gift-coupon-code-input)', _blurHandler)
.on('click.delete', '.button-delete', {'type': 'delete'}, _clickHandler)
.on('click.refresh', '.button-refresh', {'type': 'refresh'}, _clickHandler)
.on('click.addtocart', '.button-to-cart', {'type': 'add'}, _clickHandler)
.on('click.submit', '.button-submit', {'type': 'submit'}, _submitButtonHandler)
.on('submit', _submitHandler)
.on(jse.libs.theme.events.CHECK_CART(), _checkFormHandler);
$('a.toggleusebalance input[name="gv_use_balance"]').on('click', function() {
console.info('click'); location = $(this).parent('a').get(0).href;
});
done();
};
// Return data to widget engine
return module;
});