I am attempting to extend the custom options on a product. I'd like to add a number of fields that would be too much to display in the same row as the rest of the custom options. They are completely optional and so I'd like to have a link in each option value that opens a modal that will display a form to collect the other values:

Custom Option Links

Using a PHP Modifier I am able to add the link to open the modal as a child of the Magento_Ui/js/dynamic-rows/record component.

'arguments' => [
    'data' => [
        'config' => [
            'title' => __('My Custom Fields'),
            'formElement' => Container::NAME,
            'componentType' => Container::NAME,
            'component' => 'Magento_Ui/js/form/components/button',
            'actions' => [
                [
                    'targetName' => 'ns=product_form, index=my_fields_modal',
                    'actionName' => 'openModal',
                ],
            ],
            'displayAsLink' => true,
            'sortOrder' => $sortOrder,
        ],
    ],
]

This opens the window and displays the form...

enter image description here

Where I am getting stuck is finding a way to tie these values back to the associated catalog_product_option_type_value record. (Either in new columns in that table or in a new table where I can reference catalog_product_option_type_value)

Because of the current lack of documentation around the new ui components I don't have a clear understanding of how that part ties in.

Is there a way to pass the current index/row when opening the modal, in order to name the form fields in such a way that they will be saved to that table?

Any help would be greatly appreciated.

有帮助吗?

解决方案

I finally figured out a way to do this.

I will warn you, it's not pretty. If anyone has a better way of doing this, I'm all ears!

There has to be a better way of doing this, but this is what I was able to come up with after much trial and error... and it works in my scenario (Disclaimer: Your results may vary).

Basically, the idea here is to extend the modal with our own which has a few methods that take care of:

  • "un-doing" all of the various knockout bindings etc
  • changing the fields that need to be changed
  • and then re-applying the bindings.

The updateElement method below is recursive and also takes care of all child elements.
It specifically targets certain properties that I need (like elem.imports.visible).
You may need others for your situation.

My hope at the start of this was to find one method I just needed to call which would basically re-build everything, but unfortunately I was never able to find one.

My custom modal component:

define([
    'Magento_Ui/js/modal/modal-component',
    'jquery',
    'ko',
    'underscore'
], function (Modal, $, ko, _) {
    'use strict';

    return Modal.extend({
        /**
         * Set the dataScope, and related properties / actions
         *
         * @param {String} dataScope
         */
        setDataScope: function (dataScope) {
            this.originalDataScope = this.dataScope;
            this.newDataScope = dataScope;

            // Unbind modal content children
            var modalElement = $('.product_form_product_form_my_fields_modal');
            var modalContent = modalElement.find('.modal-content');
            modalContent.find('*').each(function () {
                $(this).unbind();
            });

            // Clean nodes
            ko.utils.arrayForEach(modalContent[0].childNodes, ko.cleanNode);

            // Update Element and Child Elements
            this.updateElement(this);

            // Re-apply ko bindings
            ko.applyBindings(this, modalContent[0]);

            // Remove modal
            delete this.modal;

            // Detach modal DOM element
            modalElement.detach();

            // Re-init modal content
            this.initializeContent();
        },

        /**
         * Update element recursively
         *
         * @param {Object} elem
         */
        updateElement: function (elem) {
            // Update datascope
            if (elem.dataScope) {
                elem.dataScope = elem.dataScope.replace(this.originalDataScope, this.newDataScope);
            }

            // Update parent datascope
            if (elem.parentScope) {
                elem.parentScope = elem.parentScope.replace(this.originalDataScope, this.newDataScope);
            }

            // Container changes
            if (elem.componentType === 'container') {
                // Re-initialize links
                if (elem.imports.visible) {
                    elem.imports.visible = elem.imports.visible.replace(this.originalDataScope, this.newDataScope);

                    elem.initLinks();
                }
            }

            // Input field changes
            if (elem.inputName) {
                // Dispose of all existing subscriptions for input field value
                while (elem.value._subscriptions.change[0]) {
                    elem.value._subscriptions.change[0].dispose();
                }

                // Remove imports & exports
                elem.maps.imports = {};
                elem.maps.exports = {};

                // Change input name
                elem.inputName = this.dataScopeToHtmlArray(elem.dataScope);

                // Re-initialize links
                if (elem.links.value) {
                    elem.links.value = elem.links.value.replace(this.originalDataScope, this.newDataScope);

                    elem.initLinks();
                }
            }

            // Recursively update all child elements
            if (_.isFunction(elem.elems)) {
                _.each(elem.elems(), this.updateElement, this);
            }
        },

        /**
         * Get HTML array from data scope.
         *
         * @param {String} dataScopeString
         * @returns {String}
         */
        dataScopeToHtmlArray: function (dataScopeString) {
            var dataScopeArray, dataScope, reduceFunction;

            /**
             * Add new level of nesting.
             *
             * @param {String} prev
             * @param {String} curr
             * @returns {String}
             */
            reduceFunction = function (prev, curr) {
                return prev + '[' + curr + ']';
            };

            dataScopeArray = dataScopeString.split('.');

            dataScope = dataScopeArray.shift();
            dataScope += dataScopeArray.reduce(reduceFunction, '');

            return dataScope;
        }
    });
});

In the definition for my modal I changed the component to be the one I created above.

Then, to call this component I then changed my PHP Modifier to call our method with the datascope for the current row:

'arguments' => [
    'data' => [
        'config' => [
            'title' => __('My Custom Fields'),
            'formElement' => Container::NAME,
            'componentType' => Container::NAME,
            'component' => 'Magento_Ui/js/form/components/button',
            'actions' => [
                [
                    'targetName' => 'ns=product_form, index=my_fields_modal',
                    'actionName' => 'setDataScope',
                    'params' => [
                        '${$.dataScope}'
                    ]
                ],
                [
                    'targetName' => 'ns=product_form, index=my_fields_modal',
                    'actionName' => 'openModal',
                ],
            ],
            'displayAsLink' => true,
            'sortOrder' => $sortOrder,
        ],
    ],
]

Note: A bug with Magento core code exists which will cause this to fail the second time the link is clicked. A description of the issue and my work-around can be found here: https://github.com/magento/magento2/issues/7445

许可以下: CC-BY-SA归因
scroll top