You just missing admin router, follow below code for complete solution for category custom image attribute.
Magento custom category image attribute code.
app/code/Wage/Categorylist/registration.php
<?php
\Magento\Framework\Component\ComponentRegistrar::register(
\Magento\Framework\Component\ComponentRegistrar::MODULE,
'Wage_Categorylist',
__DIR__
);
app/code/Wage/Categorylist/composer.json
{
"name": "categorylist/magento2-category-image",
"description": "Add custom category image attribute",
"require": {
"php": "~5.5.0|~5.6.0"
},
"type": "magento2-module",
"version": "1.0.0",
"license": [
"OSL-3.0",
"AFL-3.0"
],
"extra": {
"map": [
[
"*",
"Wage/Categorylist"
]
]
}
}
app/code/Wage/Categorylist/etc/module.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:Module/etc/module.xsd">
<module name="Wage_Categorylist" setup_version="1.0.0">
</module>
</config>
app/code/Wage/Categorylist/etc/di.xml
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
<type name="Wage\Categorylist\Controller\Adminhtml\Category\Thumbnailimage\Upload">
<arguments>
<argument name="imageUploader" xsi:type="object">Magento\Catalog\CategoryImageUpload</argument>
</arguments>
</type>
<virtualType name="Magento\Catalog\CategoryImageUpload" type="Magento\Catalog\Model\ImageUploader">
<arguments>
<argument name="baseTmpPath" xsi:type="string">catalog/tmp/category</argument>
<argument name="basePath" xsi:type="string">catalog/category</argument>
<argument name="allowedExtensions" xsi:type="array">
<item name="jpg" xsi:type="string">jpg</item>
<item name="jpeg" xsi:type="string">jpeg</item>
<item name="gif" xsi:type="string">gif</item>
<item name="png" xsi:type="string">png</item>
</argument>
</arguments>
</virtualType>
</config>
app/code/Wage/Categorylist/etc/adminhtml/routes.xml
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
<router id="admin">
<route id="categorylist" frontName="categorylist">
<module name="Wage_Categorylist" before="Magento_Backend" />
</route>
</router>
</config>
app/code/Wage/Categorylist/Setup/InstallData.php
<?php
namespace Wage\Categorylist\Setup;
use Magento\Framework\Module\Setup\Migration;
use Magento\Framework\Setup\InstallDataInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\ModuleDataSetupInterface;
use Magento\Catalog\Setup\CategorySetupFactory;
class InstallData implements InstallDataInterface
{
public function __construct(CategorySetupFactory $categorySetupFactory)
{
$this->categorySetupFactory = $categorySetupFactory;
}
public function install(ModuleDataSetupInterface $setup, ModuleContextInterface $context)
{
$installer = $setup;
$installer->startSetup();
$categorySetup = $this->categorySetupFactory->create(['setup' => $setup]);
$entityTypeId = $categorySetup->getEntityTypeId(\Magento\Catalog\Model\Category::ENTITY);
$attributeSetId = $categorySetup->getDefaultAttributeSetId($entityTypeId);
$categorySetup->removeAttribute(
\Magento\Catalog\Model\Category::ENTITY, 'thumbnail' );
$categorySetup->addAttribute(
\Magento\Catalog\Model\Category::ENTITY, 'thumbnail', [
'type' => 'varchar',
'label' => 'Featured Image',
'input' => 'image',
'backend' => 'Magento\Catalog\Model\Category\Attribute\Backend\Image',
'required' => false,
'sort_order' => 5,
'global' => \Magento\Eav\Model\Entity\Attribute\ScopedAttributeInterface::SCOPE_STORE,
'group' => 'General Information',
]
);
$installer->endSetup();
}
}
app/code/Wage/Categorylist/view/adminhtml/ui_component/category_form.xml
<?xml version="1.0" encoding="UTF-8"?>
<form xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="urn:magento:module:Magento_Ui:etc/ui_configuration.xsd">
<fieldset name="content">
<field name="thumbnail">
<argument name="data" xsi:type="array">
<item name="config" xsi:type="array">
<item name="dataType" xsi:type="string">string</item>
<item name="source" xsi:type="string">category</item>
<item name="label" xsi:type="string" translate="true">Thumbnail Image</item>
<item name="visible" xsi:type="boolean">true</item>
<item name="formElement" xsi:type="string">fileUploader</item>
<item name="elementTmpl" xsi:type="string">ui/form/element/uploader/uploader</item>
<item name="previewTmpl" xsi:type="string">Magento_Catalog/image-preview</item>
<item name="required" xsi:type="boolean">false</item>
<item name="sortOrder" xsi:type="number">30</item>
<item name="uploaderConfig" xsi:type="array">
<item name="url" xsi:type="url" path="categorylist/category_thumbnailimage/upload"/>
</item>
</item>
</argument>
</field>
</fieldset>
</form>
app/code/Wage/Categorylist/Controller/Adminhtml/Category/Thumbnailimage/Upload.php
<?php
namespace Wage\Categorylist\Controller\Adminhtml\Category\Thumbnailimage;
use Magento\Framework\Controller\ResultFactory;
/**
* Class Upload
*/
class Upload extends \Magento\Backend\App\Action
{
protected $baseTmpPath;
protected $imageUploader;
public function __construct(
\Magento\Backend\App\Action\Context $context,
\Magento\Catalog\Model\ImageUploader $imageUploader
) {
$this->imageUploader = $imageUploader;
parent::__construct($context);
}
public function execute() {
try {
$result = $this->imageUploader->saveFileToTmpDir('thumbnail');
$result['cookie'] = [
'name' => $this->_getSession()->getName(),
'value' => $this->_getSession()->getSessionId(),
'lifetime' => $this->_getSession()->getCookieLifetime(),
'path' => $this->_getSession()->getCookiePath(),
'domain' => $this->_getSession()->getCookieDomain(),
];
} catch (\Exception $e) {
$result = ['error' => $e->getMessage(), 'errorcode' => $e->getCode()];
}
return $this->resultFactory->create(ResultFactory::TYPE_JSON)->setData($result);
}
}
Note : When you applied abvoe complete code, upload category image show just preview image but after save category, image will not show.
this is magento version default bug.
To solve this issue by apply bellow patch.
Opne this url : https://github.com/magento/magento2/pull/5978/files
You have to override below core files code with file code from given URL.
vendor/magento/module-catalog/Controller/Adminhtml/Category/Image/Upload.php
vendor/magento/module-catalog/Controller/Adminhtml/Category/Save.php
vendor/magento/module-catalog/Model/Category.php
vendor/magento/module-catalog/Model/Category/Attribute/Backend/Image.php
vendor/magento/module-catalog/Model/Category/DataProvider.php
Now magento-2.1 working code for custom image attribute of category.
Let me know if you have any query/consern from above.
I'd like to go straight to your questions and then I'll try to make it clear on what you can actually do with the mixins plugin. So, first things first.
Implementation
The main thing here is the ability of any RequireJS plugin to completely take over the loading process of certain files.
This allows to modify the export value of a module before it will be passed as a resolved dependency.
Take a look at this sketchy implementation of what Magento custom mixins plugin is actually is:
// RequireJS config object.
// Like this one: app/code/Magento/Theme/view/base/requirejs-config.js
{
//...
// Every RequireJS plugin is a module and every module can
// have it's configuration.
config: {
sampleMixinPlugin: {
'path/to/the/sampleModule': ['path/to/extension']
}
}
}
define('sampleMixinPlugin', [
'module'
] function (module) {
'use strict';
// Data that was defined in the previous step.
var mixinsMap = module.config();
return {
/**
* This method will be invoked to load a module in case it was requested
* with a 'sampleMixinPlugin!' substring in it's path,
* e.g 'sampleMixinPlugin!path/to/the/module'.
*/
load: function (name, req, onLoad) {
var mixinsForModule = [],
moduleUrl = req.toUrl(name),
toLoad;
// Get a list of mixins that need to be applied to the module.
if (name in mixinsMap) {
mixinsForModule = mixinsMap[name];
}
toLoad = [moduleUrl].concat(mixinsForModule);
// Load the original module along with mixins for it.
req(toLoad, function (moduleExport, ...mixinFunctions) {
// Apply mixins to the original value exported by the sampleModule.
var modifiedExport = mixinFunctions.reduce(function (result, mixinFn) {
return mixinFn(result);
}, moduleExport);
// Tell RequireJS that this is what was actually loaded.
onLoad(modifiedExport);
});
}
}
});
The last and the most challenging part is to dynamically prepend the 'sampleMixinPlugin!' substring to the requested modules. To do this we
intercept define
and require
invocations and modify the list of dependencies before they will be processed by the original RequireJS load method.
It's a little bit tricky and I'd recommend to look at the implementation lib/web/mage/requirejs/mixins.js
if you wanna how it works.
Debugging
I'd recommend this steps:
- Make sure that the configuration for 'mixins!' plugin is actually there.
- Check that the path to a module is being modified. I.e. it turns from
path/to/module
to mixins!path/to/module
.
And the last but not least, requiresjs/mixins.js
has nothing to do with the main.js
or script.js
modules as they can only extend the configuration being passed from the data-mage-init
attribute:
<div data-mage-init='{
"path/to/module": {
"foo": "bar",
"mixins": ["path/to/configuration-modifier"]
}
}'></div>
I mean that the former two files don't mess with the value returned by a module, instead they pre-process configuration of an instance.
Usage Examples
To begin with I'd like to set the record straight that so called "mixins" (you're right about the misnaming) actually allow to modify the exported value of a module in any way you want. I'd say that this is a way more generic mechanism.
Here is a quick sample of adding extra functionality to the function being exported by a module:
// multiply.js
define(function () {
'use strict';
/**
* Multiplies two numeric values.
*/
function multiply(a, b) {
return a * b;
}
return multiply;
});
// extension.js
define(function () {
'use strict';
return function (multiply) {
// Function that allows to multiply an arbitrary number of values.
return function () {
var args = Array.from(arguments);
return args.reduce(function (result, value) {
return multiply(result, value);
}, 1);
};
};
});
// dependant.js
define(['multiply'], function (multiply) {
'use strict';
console.log(multiply(2, 3, 4)); // 24
});
You can implement an actual mixin for any object/function returned by a module and you don't need to depend on the extend
method at all.
Extending a constructor function:
// construnctor.js
define(function () {
'use strict';
function ClassA() {
this.property = 'foo';
}
ClassA.prototype.method = function () {
return this.property + 'bar';
}
return ClassA;
});
// mixin.js
define(function () {
'use strict';
return function (ClassA) {
var originalMethod = ClassA.prototype.method;
ClassA.prototype.method = function () {
return originalMethod.apply(this, arguments) + 'baz';
};
return ClassA;
}
});
I hope that this answers your questions.
Regards.
Best Answer
requirejs-config.js
path/to/your/mixin.js