我的管理员中有一个表单,我可以在管理员中动态添加多个文件上传字段。
这个机制是用Knockout构建的。
但是当我提交表单时,文件没有上传(因为Magento'将表单信息转移到隐藏的表单而不是提交)。
如何在提交时将动态添加的文件上载字段考虑在内?
我在Knockout中创建了一个可以创建一组选项的组件。这些选项具有标签和图像。在水下,所有这些配置都保存为JSON字符串,并且此JSON字符串存储在数据库中。除图像外,这种方法效果很好。
在我的表单中添加一个虚拟上传字段会将Magento的“隐藏”表单转换为enctype="multipart/form-data"
,但它不会包含我动态添加的文件上传字段。
第一步:我在UI组件的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>
该html
呈现的输入字段和添加新的选项提供了一个按钮-template看起来是这样的:
<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 ...
将name
根据它的范围输入字段的动态构建,但它永远是这样image_1_0_0
或image_1_2_3
等options
是可观察到的数组,addOption
增加了一个新选项,此阵:
addOption: function(column) {
column.options.push({
value: this.getSubscribedObservable(),
image: this.getSubscribedObservable()
});
},
getSubscribedObservable()
是一个返回相同的方法,ko.observable()
但是为它订阅一个更新value()
我的UIElement 的observable 的事件,有效地用JSON字符串替换textarea中的文本(如上所述)。
现在,因为所有内容都被转移到了最初的textarea(名称configuration
),所以一切都运行良好。但是文件上传字段当然不能像这样处理。
所以,我想要做的唯一的事情就是确保这些文件被发布到服务器上,这样我就可以处理文件上传从我的控制器内(我可以根据自己的动态名称(用正确的选择配合他们image_0_0_1
,image_0_0_2
等等)那么这里的问题是:如何将这些文件发布到我的服务器上?
当我提交表单时,Magento会快速创建一个隐藏的表单,但我认为它只会查看在我的UI组件中声明的字段,而不是使用JavaScript动态添加的字段。那我该怎么做呢?
或者甚至是一个更基本的例子,它将文件上传排除在等式之外:“如果表单添加了UI组件中没有的额外文本输入字段,那么如何将其提交给我的控制器?”
我已经想出了如何为默认表单字段(例如文本输入)执行此操作。通过向相关data-form-part
元素添加-attribute,它将添加到'hidden'表单:
<input type="text"
data-form-part="form_form_form"
data-bind="attr:{name:'image_' + $parentContext.$parentContext.$index() + '_' + $parentContext.$index() + '_' + $index()}"/>
但这不适用于输入file
字段,因为它们只会被转换为文本字段并获取假值,但不会上传文件(例如:) "C:\fakepath\some-image.png"
。
解决办法是:表单的内容被复制到隐藏的表单,并且只复制UI组件的XML文件中声明的字段的值。可以使用data-form-part
-attribute 添加其他字段。
我们需要做的第一件事是弄清楚,在代码中这些值被复制到这个隐藏的形式。好吧,事实证明这发生在lib/mage/utils/misc.js::submit()
:
submit: function (options, attrs) {
var form = document.createElement('form'),
data = this.serialize(options.data),
attributes = _.extend({}, defaultAttributes, attrs || {}),
field;
if (!attributes.action) {
attributes.action = options.url;
}
data['form_key'] = window.FORM_KEY;
_.each(attributes, function (value, name) {
form.setAttribute(name, value);
});
_.each(data, function (value, name) {
field = document.createElement('input');
field.setAttribute('name', name);
field.setAttribute('type', 'hidden');
field.value = value;
form.appendChild(field);
});
document.body.appendChild(form);
form.submit();
},
如您所见,传递给此函数的所有属性都将转换为新形式的隐藏输入元素。但众所周知,如果你想提交文件,你需要type="file"
,而不是hidden
。所以我们需要覆盖这个功能。
首先,我们需要查看声明的位置。经过一些回溯后,我注意到submit()
-method主要是从mageUtils
-component中使用的。
mageUtils
你可能会问这是什么?嗯,这是主题模块设置的requirejs地图。它加载lib/mage/utils/main.js
了所有utils的占位符:
define(function (require) {
'use strict';
var utils = {},
_ = require('underscore');
return _.extend(
utils,
require('./arrays'),
require('./compare'),
require('./misc'),
require('./objects'),
require('./strings'),
require('./template')
);
});
所以让我们覆盖这个文件。我通过requirejs-config.js
在我的模块中添加一个文件view/adminhtml
夹来完成此操作:
var config = {
"map": {
"*": {
"mageUtils": "Vendor_Module/js/mage/utils/main"
}
}
};
这是我修改过的main.js
:
/**
* Copyright © 2016 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
define(function (require) {
'use strict';
var utils = {},
_ = require('underscore');
return _.extend(
utils,
require('mage/utils/arrays'),
require('mage/utils/compare'),
require('mage/utils/misc'),
require('mage/utils/objects'),
require('mage/utils/strings'),
require('mage/utils/template'),
// Overrule submit-method, so it includes file uploads:
{
/**
* Serializes and sends data via POST request.
*
* @param {Object} options - Options object that consists of
* a 'url' and 'data' properties.
* @param {Object} attrs - Attributes that will be added to virtual form.
*/
submit: function (options, attrs) {
var defaultAttributes = {
method: 'post',
enctype: 'multipart/form-data'
};
var form = document.createElement('form'),
data = this.serialize(options.data),
attributes = _.extend({}, defaultAttributes, attrs || {}),
field;
if (!attributes.action) {
attributes.action = options.url;
}
data['form_key'] = window.FORM_KEY;
_.each(attributes, function (value, name) {
form.setAttribute(name, value);
});
var fileUploadField;
_.each(data, function (value, name) {
field = document.createElement('input');
// Check if this is a file upload field:
if (fileUploadField = document.querySelector('input[type="file"][name="' + name + '"]')) {
// field = fileUploadField.clone();
form.appendChild(fileUploadField);
} else {
field.setAttribute('name', name);
field.setAttribute('type', 'hidden');
field.value = value;
form.appendChild(field);
}
});
document.body.appendChild(form);
form.submit();
}
}
);
});
正如你所看到的,我包含所有默认的东西,就像原版一样main.js
,但我添加了一个额外的选项,只包含一个新的submit()
方法,有效地覆盖了一个misc.js
。我做的唯一的补充是检查是否有一个具有相同名称的文件上传字段。如果是这样,请移动(不克隆,因为这不起安全作用)输入字段到新表单,否则回退到您的默认行为。
花了一段时间,但它完成了工作。现在,我可以使用动态添加的上传字段将文件上传提交到服务器