PS 1.6 Module trigger event on Ajax Add to cart - javascript

I've been studying how PrestaShop modules work lately and it would be great if someone could point me in the right direction.
I want to trigger an event (javascript method to be exact, let's call it "MyShop") that would get all the product details in it.
I have some older code but either it was working with older version of PS either it's only for normal add to cart and not ajax.
The issue is that it doesn't even get triggered at all after pressing "add to cart" button, it's actually a piece of code from Google Analytics module.
Here is a piece of modified code from my module's main .php file that is supposed to get triggered on the add to cart, it's using "hookActionCartSave()" but I am not sure if that is still available in 1.6 or got replaced by something else or whether it just simply doesn't work with ajax and I should look elsewhere?
/**
* hook save cart event to implement addtocart and remove from cart functionality
*/
public function hookActionCartSave()
{
if (!isset($this->context->cart))
return;
$cart = array(
'controller' => Tools::getValue('controller'),
'addAction' => Tools::getValue('add') ? 'add' : '',
'removeAction' => Tools::getValue('delete') ? 'delete' : '',
'extraAction' => Tools::getValue('op'),
'qty' => (int)Tools::getValue('qty', 1)
);
$cart_products = $this->context->cart->getProducts();
if (isset($cart_products) && count($cart_products))
foreach ($cart_products as $cart_product)
if ($cart_product['id_product'] == Tools::getValue('id_product'))
$add_product = $cart_product;
if ($cart['removeAction'] == 'delete')
{
$add_product_object = new Product((int)Tools::getValue('id_product'), true, (int)Configuration::get('PS_LANG_DEFAULT'));
if (Validate::isLoadedObject($add_product_object))
{
$add_product['name'] = $add_product_object->name;
$add_product['manufacturer_name'] = $add_product_object->manufacturer_name;
$add_product['category'] = $add_product_object->category;
$add_product['reference'] = $add_product_object->reference;
$add_product['link_rewrite'] = $add_product_object->link_rewrite;
$add_product['link'] = $add_product_object->link_rewrite;
$add_product['price'] = $add_product_object->price;
$add_product['ean13'] = $add_product_object->ean13;
$add_product['id_product'] = Tools::getValue('id_product');
$add_product['id_category_default'] = $add_product_object->id_category_default;
$add_product['out_of_stock'] = $add_product_object->out_of_stock;
$add_product = Product::getProductProperties((int)Configuration::get('PS_LANG_DEFAULT'), $add_product);
}
}
if (isset($add_product) && !in_array((int)Tools::getValue('id_product'), self::$products))
{
self::$products[] = (int)Tools::getValue('id_product');
$ga_products = $this->wrapProduct($add_product, $cart, 0, true);
if (array_key_exists('id_product_attribute', $ga_products) && $ga_products['id_product_attribute'] != '' && $ga_products['id_product_attribute'] != 0)
$id_product = $ga_products['id_product_attribute'];
else
$id_product = Tools::getValue('id_product');
if (isset($this->context->cookie->MyShop_cart))
$MyShop_cart = unserialize($this->context->cookie->MyShop_cart);
else
$MyShop_cart = array();
if ($cart['removeAction'] == 'delete')
$ga_products['quantity'] = -1;
elseif ($cart['extraAction'] == 'down')
{
if (array_key_exists($id_product, $MyShop_cart))
$ga_products['quantity'] = $MyShop_cart[$id_product]['quantity'] - $cart['qty'];
else
$ga_products['quantity'] = $cart['qty'] * -1;
}
elseif (Tools::getValue('step') <= 0) // Sometimes cartsave is called in checkout
{
if (array_key_exists($id_product, $MyShop_cart))
$ga_products['quantity'] = $MyShop_cart[$id_product]['quantity'] + $cart['qty'];
}
$MyShop_cart[$id_product] = $ga_products;
$this->context->cookie->MyShop_cart = serialize($MyShop_cart);
$this->didCartChange = 1;
}
}
I know that regarding what I am looking for a lot is handled in blockcart/ajax-cart.js and controllers/front/CartController.php but I am not precisely sure where or what to look for and this way I was hoping someone will lead me to a proper direction or save me some time on searching.
I also assume that regarding the javascript potion I have to modify the template itself, this is a piece of code from my module's header.tpl:
var MyShop_blockcart_log = function(json) {
var products = json.products;
var product;
var ps = [];
var p;
for(var i=0; i<products.length; i++) {
product = products[i];
p = {};
p.id = product.id;
p.link = product.link;
p.imageUrl = product.id_image.replace("medium_default", "large_default");
p.quantity = product.quantity;
p.name = product.name;
p.price = "{$currency->sign} " + (parseInt(product.price_float) / product.quantity).toFixed(2);
ps.push(p);
}
MyShop("log", "product", "cart", ps);
}
That code does not cause any errors, it just doesn't get triggered after adding products to the cart and it should.
Do I have to bind it to the add to cart button via my header.tpl file for example? And if yes then how could I do it?
Thanks in advance,
Cheers.

The hookActionCartSave only executes on cart->add() and cart->update(). But when CartController updates/adds a product, if the cart already exists for that customer, it won't executes neither one of them.
I would recommend using the actionBeforeCartUpdateQty:
Hook::exec('actionBeforeCartUpdateQty', array(
'cart' => $this,
'product' => $product,
'id_product_attribute' => $id_product_attribute,
'id_customization' => $id_customization,
'quantity' => $quantity,
'operator' => $operator,
'id_address_delivery' => $id_address_delivery,
'shop' => $shop,
'auto_add_cart_rule' => $auto_add_cart_rule,
));
It might suit your needs. Or look for another hook. In the last case scenario, you could override the CartController->postProcess and check if it's trying to add/remove products. I used this override to let the user add the maximum amount available to cart. Example, if stock is 5, and user tries to add 6, it will add 5 and not send an error. It also checks for product used in packs. If same product is in 2 pack, user can't order max stock of both packs.

Related

Swapping out Constants for a Function

I'm trying to refactor the following code, which depending on the top level domain of the recipients email, changes where you click through to in my button. I currently do this with two consts, which I need to refactor into only one.
const CCENTERURL_AT= `${HOSTURL_AT}/ccenter/zendesk/landing/`;
const CCENTERURL = `${HOSTURL}.com/ccenter/zendesk/landing/`;
const recipientEmail = data.ticket.recipient;
var cCenterUrl;
if(recipientEmail.indexOf(".com") > 0)
{
cCenterUrl = getCcenterUrl(zendeskID)
}else{
cCenterUrl = getAustrianCcenterUrl(zendeskID)
}
function getCcenterUrl(ticketID) {
const cCenterTicketUrl = CCENTERURL + ticketID ;
return cCenterTicketUrl;
}
// Get Austrian Ccenter Ticket Url using Zendesk ticket ID
function getAustrianCcenterUrl(ticketID) {
const cCenterTicketUrlAustria = CCENTERURL_AT + ticketID ;
return cCenterTicketUrlAustria;
}
I know I should be able to create a function which will take recipient Email`s top-level domain as parameter and return appropriate URL for CCENTERURL. But no matter what I've tried its become overcomplicated or hasn't worked. I would be interested to hear peoples opinions on either how I can achieve my goal or even how it would be better to go about this!
So you basically want to combine these into one function and use a string template like this.
const HOSTURL = 'example.com/'
const HOSTURL_AT = 'example.au/'
const reAu = /\.au$/;
const getTicketURL = (
(mail, id) => `${ reAu.test(mail) > 0 ? HOSTURL : HOSTURL_AT }ccenter/zendesk/landing/${id}`
);
// Test AU
console.log(getTicketURL('foo#google.com.au', 'ABC1231'))
// Test US
console.log(getTicketURL('foo#google.com', 'ABC1231'))

Value is not changing in real time -- VueJS

I am using a JS class, I have following code:
class Field {
public Value = null;
public Items = [];
public UniqueKey = null;
public getItems() {
let items = [...this.Items];
items = items.filter((item) => {
if (item.VisibleIf) {
const matched = item.VisibleIf.match(/\$\[input:(.*?)\]/g);
if (matched?.length) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
item.VisibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
return JSON.parse('' + eval(item.VisibleIf));
}
}
}
}
}
return true;
});
return items;
}
public getInputTitle() {
let title = this.Title;
const matched = title.match(/\$\[input:(.*?)\]/g);
if (matched?.length && title) {
const srv = Service.getInstance();
for (let match of matched) {
match = match.slice(8, -1);
if (srv.Fields?.length) {
let found = srv.Fields.find((x) => x.UniqueKey === match);
if (found) {
title = title.replace(`$[input:${match}]`, found.Value ?? '');
}
}
}
}
return title;
}
}
Now I have a Vue component:
<div v-for="Field in Fields" :key="Field.UniqueKey">
<v-select
v-if="Field.Type == 'Select'"
:label="Field.getInputTitle()"
v-model="Field.Value"
:items="Field.getItems()"
item-text="Value"
item-value="Id"
/>
<v-input
v-else-if="Field.Type == 'Input'"
v-model="Field.Value"
:label="Field.getInputTitle()"
/>
</div>
// JS
const srv = Service.getInstance();
Fields = srv.getFields(); // <- API call will be there.
So basically, data comes from an API, having Title as Input $[input:uniqueKey], in a component I am looping over the data and generating the fields. See getInputTitle function in Field class, it works very well. All the fields which are dependent on the $[input:uniqueKey] are changing when I start typing into that field on which other fields are dependent.
Now I have pretty much same concept in the getItems function, so basically, what I want to do is whenever I type into a field and that field exists in the VisibleIf on the Items, the VisibleIf will be like '$[input:uniqueKey] < 1', or any other valid JavaScript expression which can be solved by eval function. But the getItems function is called only 1st time when page gets loaded, on the other hand the getInputTitle function which is pretty much same, gets called every time when I type into the field.
I tried to explain at my best, I will provide any necessary information if needed.
Any solution will be appreciated. Thanks.
You are updating the Object itself in here:
item.VisibleIf = item.VisibleIf.replace( `$[input:${match}]`, found.Value ?? '' );
Even though you tried to copy the array, but you have done shallow copy of the object in here: let items = [...this.Config.Items];
I suggest the following solution:
const visibleIf = item.VisibleIf.replace(
`$[input:${match}]`,
found.Value ?? ''
);
const val = '' + helpers.evalExp('' + visibleIf);
if (helpers.isJSON(val)) {
return JSON.parse(val);
}
Means instead of changing the VisibleIf object, just store it into the variable and just use that.
I hope that it will fix your issue. Let me know if it works.

How to prevent a div from increasing the value of an upvote after one click using react

I have this div that perform an onclick event by increasing the value of an upvote when a user click on the div. It increment which is fine, but I only want it to increment only once even when the user clicks on the div multiple times.
Here is my code
btnUpvote(data) {
let feeds = [...this.state.feeds]
let feed = feeds.find(x => x.id === data.id)
// feed.upvote +1
let get = feed.upvote + 1
console.log(get)
if (feed.upvote !== get) {
}
this.setState({
feeds
})
}
The value of the feed.upvote is stored in an array of object, any help would be appreciated.
Try adding this condition :
let get = 0
if(!feed.upvote){
get= feed.upvote + 1}
You could use an array to store the id which has already been upvoted.
Check it and handle your clicks accordingly.
let allowOneClicks = [];
function allowOnce(id, callback) {
if (allowOneClicks.includes(id)) return;
allowOneClicks.push(id);
callback();
}
In your case, it can be like this.
// outside your component;
const upvotedIds = [];
btnUpvote(data) {
let feeds = [...this.state.feeds]
let feed = feeds.find(x => x.id === data.id)
if (upvotedIds.includes(id)) return;
upvotedIds.push(id);
// upvote here.
}
you can try this.
onHandleClick = (id) => {
this.setState({
feed: {
...this.state.feed,
[id]: (this.state.feed[id] || 0) + 1
}
})
}

Managing resources in lookup filter

My client wants to be able to filter jobplans with the selected asset. To be able to do that, I have developped a function that filters the results based on the custom resource jpassetsplink:
filterJobPlansForLookup: function(eventContext){
var workOrderSet = CommonHandler._getAdditionalResource(eventContext,"workOrder");
var jobPlanSet = CommonHandler._getAdditionalResource(eventContext,"jobPlanResource");
jobPlanSet._lookupFilter = null;
var assetSet = null;
var assetnum = null;
var itemnum = null;
var jpAssetSpLinkSet = null;
//CommonHandler._clearFilterForResource(this,jobPlanSet);
var siteid = workOrderSet.getCurrentRecord().get("siteid");
if(siteid == null){
siteid = UserManager.getInfo("defsite");
}
if(workOrderSet.getCurrentRecord() != null){
assetnum = workOrderSet.getCurrentRecord().get("asset");
assetSet = CommonHandler._getAdditionalResource(eventContext,"additionalasset");
CommonHandler._clearFilterForResource(eventContext, assetSet);
assetSet = assetSet.clearFilterAndSort().filter('siteid == $1', siteid)
if (assetnum != null){
var asset = assetSet.find('assetnum == $1', assetnum);
if (asset && asset.length>0){
itemnum = asset[0].get('itemnum');
}
}
}
var filter = [{siteid: siteid, status: "ACTIF"}];
if (assetnum != null){
jpAssetSpLinkSet = CommonHandler._getAdditionalResource(eventContext,"jpassetsplinkResource");
jpAssetSpLinkSet._lookupFilter = null;
CommonHandler._clearFilterForResource(eventContext, jpAssetSpLinkSet);
var filteredJpAssets = null;
if (itemnum == null){
filteredJpAssets = jpAssetSpLinkSet.clearFilterAndSort().filter('assetnum == $1', assetnum);
} else {
filteredJpAssets = jpAssetSpLinkSet.clearFilterAndSort().filter('itemnum == $1', itemnum);
}
Logger.trace("[WODetailExtensionHandler] Found " + filteredJpAssets.data.length + " links out of " + jpAssetSpLinkSet.count() );
if(filteredJpAssets && filteredJpAssets.data.length>0){
filter = [];
filteredJpAssets.data.forEach(function(jpAsset){
filter.push({jpnum: jpAsset.get("jpnum"), siteid: siteid, status: "ACTIF"});
});
}
}
jobPlanSet.lookupFilter = filter;
}
With the right circumstances this code works. There are multiple problems with it though:
1- When searching for an asset, the resulting filter is applied to the resource and cannot seem to be removed. If I search an asset in the asset lookup, when the execution gets to this function, the resource data is still filtered and calls to CommonHandler._clearFilterForResource, assetSet.clearFilterAndSort() or directly changing the _lookupFilter property does not work. This sometimes results in the impossibility to find the selected asset in the asset resource, thus the filtering ends up failing.
2- Not searching and directly inputing the desired asset leads to another problem. Since there is no filter on the resource, only the number of entries specified by pageSize is loaded. In my case, pageSize is set at 2000 for the asset resource. That means if the selected asset is not in the 2000 first entries, it is not found by the function, thus the filtering fails.
3- If the filter manages to work, it seems to block further filtering by jpnum or description in the jobplan lookup.
To conclude, here's my question: Is there a way to manage resources so that these problems do not occur ? Any tip is appreciated.
Thanks,

Meteor: Underscore _findWhere iteration through loop objects only works in chrome console, in app it says undefined

I'm trying to fetch an object 'single Post' within an object 'Posts' from a json file within meteor, which looks like this.
I found an effective way of doing it, using underscore findWhere to get to it. this is the code
_.findWhere(_.findWhere(CategoryCollection.find().fetch(),
{"_id":"CategoryPublication-5"}).posts,{"ID":46});
however when i put this into meteor, i'm getting undefined
this is the code i used
Template.CategoryArticleSingle.helpers({
articles: function () {
var id = FlowRouter.getParam('ID')
var category = FlowRouter.getParam('category')
console.log(CategoryCollection.find().fetch());
let match = _.findWhere(_.findWhere(CategoryCollection.find().fetch(), {"_id":category}).posts,{"ID": id});
console.log("match",id,category,match);
return match;
}
});
Why am i getting undefined
update.
would this be correct? i substituted the 47 id, with just id so i can use it for any link.
Im getting "category" is read-only error.
Template.CategoryArticleSingle.helpers({
articles: function () {
var id = FlowRouter.getParam('ID')
var category = FlowRouter.getParam('category')
console.log(CategoryCollection.find().fetch());
const category = CategoryCollection.find().fetch().find(c => c._id === id);
let post = null;
if (category) {
post = category.posts.find(p => p.ID === id);
}
console.log("post",id,category,post);
return post;
}
});
There's no need to use lodash/underscore's findWhere. This functionality is built into ES2015. Also, you may consider breaking up the code into a few lines to make it more legible.
const category = CategoryCollection.find().fetch().find(c => c._id === 'CategoryPublication-5');
let post = null;
if (category) {
post = category.posts.find(p => p.ID === 47);
}

Categories

Resources