knockoutJS subscribe observable - woocommerce

I am new to KnockoutJS and on a steep learning curve at the moment. I am using Gravity Forms and the Nested Forms Addon from Gravity Wiz. I have linked the Nested Form to products setup using WooCommerce.
Some of my products are Bundled products so when I select these items in my Nested Form I am trying to add the bundled products linked products to my Parent Form and not the bundled product itself. This is where Knockout JS comes in. I have subscribed to the ViewModel so I can see if entries are added/edited/deleted.
I can see my bundled product in the entries as shown in the code below, before this point I am doing an Ajax call to the server and I have got the list of linked products but I don't know how to append/bind them to my viewModel? Any help appreciated.
self.init = function() {
//alert("Its a bundled item");
get the content of the Nest Form Modal
var gpnf = window.GPNestedForms_2_1;
//subscribe to the ViewModel
gpnf.viewModel.entries.subscribe( function( entries ) {
// I can see my bundled product here
console.log( entries );
self.AppendBundledItem(entries, BundleProdArray);
} );
}
self.AppendBundledItem = function( entries, bp ) {
//loop through bp and append to entries then return to ViewModel
}
self.init();

Related

Silverstripe 4 - Adding a FormAction via getCMSFields

Goal:
I have a DataObject called "Event". This is in a managed_model for "EventsAdmin" (extending ModelAdmin). When editing an Event, I want a tab on the record called "Moderation" that has a few fields and two buttons: "Approve" and "Reject". These two buttons call an action each that performs relevant actions.
Event extends DataObject
public function getCMSFields() {
$fields = parent::getCMSFields();
$eventStatus = $fields->dataFieldByName("EventStatus")
->setTitle('Current Status')
->setDisabled(true);
$approveButton = FormAction::create('doApproveEvent', _t('SiteBlockAdmin.Approve', 'Approve'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-success font-icon-check-mark-circle');
$rejectButton = FormAction::create('doRejectEvent', _t('SiteBlockAdmin.Reject', 'Reject'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-danger font-icon-cancel-circled');
$fields->addFieldsToTab('Root.Moderation', [
$eventStatus,
$approveButton,
$rejectButton
]);
return $fields;
}
This displays the buttons just fine. But they don't do anything. So I am trying to work out how they can plug into action methods doApproveEvent and doRejectEvent (And where they should go)
I did find docs that led me to adding the buttons to the action bar at the bottom of the CMS page via updateFormActions(). But this isn't what I want as the other fields I am adding above the buttons are part of the Approve/Reject process. Here is the code for this method. This works fine barring the buttons are not in a logical place for the process I'm trying to create.
class CMSActionButtonExtension extends DataExtension
{
public function updateFormActions(FieldList $actions)
{
$record = $this->owner->getRecord();
if (!$record instanceof Event || !$record->exists()) {
return;
}
$approveButton = FormAction::create('doApproveEvent', _t('SiteBlockAdmin.Approve', 'Approve'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-success font-icon-check-mark-circle');
$rejectButton = FormAction::create('doRejectEvent', _t('SiteBlockAdmin.Reject', 'Reject'))
->setUseButtonTag(true)
->addExtraClass('btn-outline-danger font-icon-cancel-circled');
$actions->push($approveButton);
$actions->push($rejectButton);
}
public function doApproveEvent($data, $form) {
$record = $this->owner->getRecord();
// Approve logic
}
public function doRejectEvent($data, $form) {
$record = $this->owner->getRecord();
// Reject logic
}
}
The above Extension is attached to GridFieldDetailForm_ItemRequest
extension.yml
SilverStripe\Forms\GridField\GridFieldDetailForm_ItemRequest:
extensions:
- My\Namespace\CMSActionButtonExtension
Interestingly, if I have both sets of buttons on the page at the same time, the updateFormActions option works while my desired option still doesn't. Despite the buttons being of identical markup and sitting inside the exact same form tag. I assume that has something to do with how Silverstripe loads the main content panel and the DOM.
Any thoughts on achieving this? Anyone seen a button added to the main CMS panel in a module that I could take a look at? I found this post from 5 years ago, but it's for SS3 and the answer doesn't work for me.
Short answer:
you have to add custom FormActions through an Extension on the Controller that controls the form (or on the form itself
Long Answer:
A bit of background on how SilverStripe does forms:
Generally speaking, forms are always served through Controllers/RequestHandlers (they need to be accessible on some route, usually that's an Action on a Controller that is often named Form, EditForm, ItemEditoForm, ...).
Fields
Inside the CMS you rarely ever have to create your own form, that's done by the CMSs built in Controllers/RequestHandlers for the admin area (GridFieldDetailForm_ItemRequest in this case).
Basically (pseudo code here), what those controllers do is:
public function EditForm() {
$fields = $myCurrentlyEditingDataObject->getCMSFields();
$actions = ...;
$validator = ...;
$this->updateFormActions(&$actions);
$form = new Form('ItemRequestForm', $fields, $actions, $validator);
$this->updateItemEditForm(&$form); // or $this->updateEditForm()
return $form;
}
So, getCMSFields() and in some cases getCMSActions()/getCMSValidator() (not sure if those 2 are still used in SilverStripe 4.x), you can add things to the form, without ever seeing the form object.
Also, the getCMSFields() will always be put into the ``` section of the Form, that's why your button is somewhere in the middle with all the fields and not with the other actions.
Submission
When a form is submitted (eg to /admin/pages/edit/EditForm/265/field/NameOfMyGridField/item/542/ItemEditForm), it will call the action GridFieldDetailForm_ItemRequest->ItemEditForm() which returns the Form object where subsequently FormRequestHandler->httpSubmission() is called. This will then look at the submitted data to figure out what action was clicked (eg $_REQUEST['action_doApproveEvent']) and try to find that action.
The way it tries to find that, is checking if it itself has a method called doApproveEvent, if that fails, it will try Form->getController()->doApproveEvent() or something like that. In the case of a GridField, that controller is GridFieldDetailForm_ItemRequest which means it will try to call GridFieldDetailForm_ItemRequest->doApproveEvent()
So, that means DataObject->getCMSFields() lets you easily add FormFields (and FormActions) into your form body.
But it does not provide a means of adding a method to handle the submission.
That's why, for custom actions you need to modify the Controller (GridFieldDetailForm_ItemRequest in this case).
You are doing this by creating a Extension which you attached to GridFieldDetailForm_ItemRequest.
Any method in your Extension is added to the thing it's attached to, so if you add a method called updateFormActions, it will kind of become GridFieldDetailForm_ItemRequest->updateFormActions().
And if you recall from earlier, the controller will call $this->updateFormActions() during the creation of the form.
Additionally, as I explained earlier, when a FormAction is named doApproveEvent it will look for a GridFieldDetailForm_ItemRequest->doApproveEvent(), which now exists because you added it through that Extension.
So, in summary: you have to add custom FormActions through an Extension on the Controller that controls the form (or on the form itself
PS: the old post from
bummzack you linked to worked in 3.x, because the Controller in his example that created the form was an instance of LeftAndMain.

WooCommerce Bookings: Triggering AJAX Recalculation of Booking Cost on Product Page

I've added a couple new fields to the date-picker.php template in WooCommerce Bookings and need to trigger the AJAX recalculation of the booking cost on the product page when these fields are updated.
Currently this recalculation is triggered when the standard fields in date-picker.php are updated. However, I will be using the additional fields to recalculate the duration and booking cost so I need this AJAX recalculation triggered when updates are made to the new fields as well.
How can I trigger the AJAX recalculation? I have tried simply triggering a change event on one of the standard fields, but that doesn't seem to work.
I had the same issue. If it helps someone in the future, heres my work around :
Go to
/wp-content/plugins/woocommerce-bookings/templates/single-product/add-to-cart/booking.php
This is the template for the single product booking form.
You can then move one of the actions such as <?php do_action( 'woocommerce_before_add_to_cart_button' ); ?> (or create your own action) within the <div id="wc-bookings-booking-form"/>.
Now add a custom field or code to this action (I used wc-fields-factory). When you change a field value within the booking form the AJAX recalculation is triggered.
You can hook into this trigger in your functions.php file (within your theme) to alter the cost based on the custom fields you have added like so :
add_filter( 'booking_form_calculated_booking_cost', 'my_booking_cost', 10, 3 );
function my_booking_cost( $booking_cost, $booking, $posted ) {
/* use fields here to show a new booking cost*/
$my_time = $posted[ <name of custom field> ]; //access field within booking form
$booking_cost = 1; //whatever your calc is
return $booking_cost;
}

WP / Elementor Intercept Form and Redirect with Data

I have a form built in Elementor that I am looking to intercep, process the data and forward onto a third party then subsequently show the data on a "confirm" card.
I am able to build this whole process as a single page, setting each as display none with CSS then showing / hiding with JS as I receive AJAX responses. This isn't ideal as it breaks with JS turned off.
I haven't been able to find the right Elementor hook and way to populate a new page with PHP, has anyone had experience with this?
There are a few methods to POST data to another url from an Elementor web form.
One is using many of the API integrations such as Mailchimp, ActiveCampaign, Zapier etc. (see https://docs.elementor.com/article/281-form-faq) and (https://docs.elementor.com/category/405-integrations)
Another (very limited method) is by using the form's Action after Submit and choosing "redirect" and then using each field's short code as individual data strings in the url such as:
mysite.com/thank-you?fname=[field id="fname"]&lname=[field id="lname"]
You can even build your /thank-you/ page in Elementor to GET that data and populate Elementor elements such as text, title, links etc with the form field data. For example, I could drop a text element on the /thank-you/ page and choose dynamic instead of typing in the text area and from the dynamic drop down, choose "request parameter" and for the "type" choose GET and for the param name use your unique url keys such as fname, lname etc. You can even set prefix, suffix and even fallback text along with it.
Lastly, here is a write up on how to back end code passing data externally (https://developers.elementor.com/forms-api/#Form_New_Record_Action).
// A send custom WebHook
add_action( 'elementor_pro/forms/new_record', function( $record, $handler ) {
//make sure its our form
$form_name = $record->get_form_settings( 'form_name' );
// Replace MY_FORM_NAME with the name you gave your form
if ( 'MY_FORM_NAME' !== $form_name ) {
return;
}
$raw_fields = $record->get( 'fields' );
$fields = [];
foreach ( $raw_fields as $id => $field ) {
$fields[ $id ] = $field['value'];
}
// Replace HTTP://YOUR_WEBHOOK_URL with the actuall URL you want to post the form to
wp_remote_post( 'HTTP://YOUR_WEBHOOK_URL', [
'body' => $fields,
]);
}, 10, 2 );
And a thread with many more examples integrating with different APIs using that template as a primer (https://github.com/elementor/elementor/issues/2397)

Adding a Drupal Commerce line item to an order from a module

I'm trying to create a gift wrapping module for Drupal commerce. I have created the checkout pane that has a select box for the user to choose if they want their order gift wrapped (and a field to select the giftwrap price on the configuration form). I've also created a giftwrap line item type. In the pane's base_checkout_form_submit() function I would like to create a giftwrap line item that is added to the order alongside the products. This is what I've got so far:
/**
* Implements base_checkout_form_submit()
*/
function commerce_giftwrap_pane_checkout_form_submit($form, &$form_state, $checkout_pane, $order) {
$default_currency_code = commerce_default_currency();
if ($balance = commerce_payment_order_balance($order)) {
$default_currency_code = $balance['currency_code'];
}
// Create the new line item.
$line_item = commerce_line_item_new('giftwrap', $order->order_id);
$line_item->line_item_label = 'Gift Wrapping';
$line_item->quantity = 1;
$line_item->commerce_unit_price['amount'] = variable_get('commerce_giftwrap_price', '2.00');
$line_item->commerce_unit_price['currency_code'] = $default_currency_code;
commerce_line_item_save($line_item);
}
I haven't wrapped it in an if statement yet, I wanted to get it working first. This code is creating a line item in the database however it isn't adding the line item to shopping cart contents view on the checkout review page. I've altered the shopping cart view to include product line items and my newly created giftwrap line items.
Any help on this would be greatly appreciated.
I've worked this out and uploaded the module as a sandbox project on drupal.
I'm not sure if this helps, but you can try to implement: hook_commerce_line_item_type_info().

Magento layered navigation on custom product collection

I have been working on a custom module for Magento (ver. 1.8.0.0) that shows a list of related products of a certain product.
In order to achieve this I have created my own module by overwriting the Mage_Catalog_Block_Product_List class.
Basically here's how it works:
From a controller I catch the products entity_id and I store the product in the registry so I can use it inside my custom written Block which is called list.php
Here is the method that fills the product collection:
protected function _getProductCollection()
{
if (is_null($this->_productCollection)) {
$prod = Mage::registry('chosenproduct');
$this->_productCollection = $prod->getRelatedProductCollection()
->addAttributeToSelect('required_options')
->addAttributeToFilter(array(array('attribute'=>'accessory_manufacturer','neq'=>false)))
->addAttributeToSort('position', 'asc')
->addStoreFilter()
->setPageSize(30)
->setCurPage(1);
;
$this->_addProductAttributesAndPrices($this->_productCollection);
Mage::getSingleton('catalog/product_visibility')->addVisibleInCatalogFilterToCollection($this->_productCollection);
$this->setProductCollection($this->_productCollection);
}
return $this->_productCollection;
}
I also added the following in the layout .xml of my custom module to make sure the layered navigation shows:
<reference name="left">
<block type="catalog/layer_view" name="catalog.leftnav" after="currency" template="catalog/layer/view.phtml"/>
</reference>
The layered navigation shows, but it seems that it is taking all products as collection instead of the custom collection that is used in the method I added above.
I also know that I can get the catalog/layer using this $layer = Mage::getSingleton('catalog/layer');
The layer class also has a method called prepareProductCollection and setCollection but for some reason I can't get it to work.
Any help on this?
Basically I want to have the layered navigation for the products that are in the custom collection.
Thanks,
I just managed to achieve what I wanted. I have overwritten both the Mage_Catalog_Model_Layer class and the Mage_Catalog_Model_Category
Both now have a new variable called $_customCollection: protected $_customProductCollection;
I have overwritten the getProductCollection() in both classes and I added this in the beginning of the method:
if(isset($this->_customProductCollection)){
return $this->_customProductCollection;
}
I have also a method that allows me to set this "customProductCollection" inside both these classes. Once It's set, the rest of the data of the layered navigation/category is based on this collection.
;)

Resources