Short story:
I have a form in my admin in which I can dynamically add multiple file upload fields in the admin. This mechanism is built with Knockout. But when I submit the form the files are not uploaded (since Magento 'transfers' the form information to a hidden form that gets submitted instead). How can I make it so that dynamically added file upload fields are taken into account when submitting?
Long story:
I've created a component in Knockout that can create a set of options. These options have a label and an image. Under water, all this configuration is saved into a JSON-string, and this JSON string is stored in the database. This works perfectly, except for the images.
I've already tried this solution but these aren't dynamically added upload fields. Nevertheless, adding a dummy upload field to my form will convert Magento's 'hidden' form to enctype="multipart/form-data"
, but it will not include my dynamically added file upload fields.
First step: I added a custom field in my UI Components' XML:
<field name="configuration">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<!-- Change the component: -->
<item name="component" xsi:type="string">Vendor_Module/js/form/element/configurator</item>
<!--main template for form field that renders elementTmpl as a child template-->
<item name="template" xsi:type="string">ui/form/field</item>
<!-- Set the template :-->
<item name="elementTmpl" xsi:type="string">Vendor_Module/form/element/configurator</item>
<item name="dataType" xsi:type="string">text</item>
<item name="label" xsi:type="string" translate="true">Configuration</item>
<item name="formElement" xsi:type="string">textarea</item>
<item name="source" xsi:type="string">form</item>
<item name="sortOrder" xsi:type="number">30</item>
<item name="dataScope" xsi:type="string">configuration</item>
<item name="validation" xsi:type="array">
<item name="required-entry" xsi:type="boolean">true</item>
</item>
</item>
</argument>
</field>
The html
-template that renders the input fields and provides a button for adding a new options looks like this:
<textarea class="admin__control-textarea" data-bind="
value: value,
valueUpdate: valueUpdate,
hasFocus: focused,
attr: {
name: inputName,
cols: cols,
rows: rows,
'aria-describedby': noticeId,
placeholder: placeholder,
id: uid,
disabled: disabled
}"
/>
... some knockout template code ...
<div class="form-configurator__options">
<label data-bind="i18n: 'Options'"></label>
<ul data-bind="foreach:options">
<li>
<input type="text" data-bind="value:value"/>
<input type="text" data-bind="value:image"/>
<input type="file" data-bind="attr:{name:'image_' + $parentContext.$parentContext.$index() + '_' + $parentContext.$index() + '_' + $index()}"/>
</li>
</ul>
<a href="#" class="form-configurator__button" data-bind="i18n: 'Add New Value', click: $parents[1].addOption.bind($parents[1])"></a>
</div>
... some knockout template code ...
The name
of the input field is dynamically built according to it's scope, but it will always be something like image_1_0_0
or image_1_2_3
etc. The options
is an observable array and addOption
adds a new option to this array:
addOption: function(column) {
column.options.push({
value: this.getSubscribedObservable(),
image: this.getSubscribedObservable()
});
},
getSubscribedObservable()
is a method that returns the same as ko.observable()
but subscribes an event to it that updates the value()
observable of my UIElement, effectively replacing the text in the textarea with the JSON string (as I mentioned above).
Now, because everything is transferred into the initial textarea (with the name configuration
), everything works well. But the file upload fields of course cannot be processed like this.
So the only thing I want to do is make sure these files are posted to the server, so I can handle the file uploads from within my controller (I can match them with the correct option according to their dynamic names (image_0_0_1
, image_0_0_2
, etc.). So the question here is: how can I get those files posted to my server?
When I submit the form, Magento quickly creates a hidden form, but I think it only looks at the fields that are declared in my UI Component, not the fields that are dynamically added with JavaScript. So how can I do this?
Or even a more basic example that leaves file uploads out of the equation: "If a form adds an extra text input field that is not available in the UI Component, how can it get submitted to my controller?"
I already figured out how to do this for default form fields (like text inputs for example. By adding a data-form-part
-attribute to the element in question, it gets added to the 'hidden' form:
<input type="text"
data-form-part="form_form_form"
data-bind="attr:{name:'image_' + $parentContext.$parentContext.$index() + '_' + $parentContext.$index() + '_' + $index()}"/>
But this doesn't work for file
-input fields, since they would just be transformed to text-fields and get the fake value, but no file upload (for example: "C:\fakepath\some-image.png"
).
Any help would be greatly appreciated.