Q:

Magento 2 : Add Hero Image Upload Field on CMS page

How to add image upload field to a CMS page and show this on top of my CMS page as Hero image like shown below?
Maybe there are simple solutions/tutorials, but I can't find right one.

A:

First you have to create your own module to override the save action and dataProvider for cms page. 
If you don't know how to create module Please refer this link http://inchoo.net/magento-2/how-to-create-a-basic-module-in-magento-2/.
After creating a module, follow the following steps:
Step 1. Add column for custom image on cms_page table 
Create InstallSchema.php under [Vendor][Module]\Setup. Your file location will be [Vendor][Module]\Setup\InstallSchema.php
    

namespace [Vendor]\[Module]\Setup;

use Magento\Framework\Setup\InstallSchemaInterface;
use Magento\Framework\Setup\ModuleContextInterface;
use Magento\Framework\Setup\SchemaSetupInterface;
use Magento\Framework\DB\Ddl\Table;

class InstallSchema implements InstallSchemaInterface
{
    public function install(SchemaSetupInterface $setup, ModuleContextInterface $context)
    {
        $installer = $setup;
        $installer->startSetup();
        $connection = $installer->getConnection();

        $connection->addColumn('cms_page','your_image_field_name',['type' =>\Magento\Framework\DB\Ddl\Table::TYPE_TEXT,'comment' => 'Your Image Field Name']);
        $installer->endSetup();
    }
}

If your module is already activated, please delete your module from setup_module table.
After Creating InstallSchema.php, open your terminal and hit following commands. It will add column to database table and clear caches.
php bin/magento cache:flush;
php bin/magento setup:upgrade;
php bin/magento setup:di:compile;
rm -rf var/generation/* var/di/* var/cache/* var/page_cache/* ;
rm -rf pub/static/frontend pub/static/_requirejs pub/static/adminhtml ;

Step 2. Add image field 
Create cms_page_form.xml  inside the folder [Vendor]/[Module]/view/adminhtml/ui_component . Your file location will  be like [Vendor]/[Module]/view/adminhtml/ui_component/cms_page_form.xml. 
Add the following code.
<?xml version="1.0" encoding="UTF-8"?>
<!--
/**
* Copyright © 2013-2017 Magento, Inc. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<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="your_image_field_name">
            <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">Your image field Name</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="required" xsi:type="boolean">false</item>
                    <item name="uploaderConfig" xsi:type="array">
                        <item name="url" xsi:type="url" path="[module]/cms_heroimage/upload"/>
                    </item>
                </item>
            </argument>
        </field>
    </fieldset>
</form>

This will generate field under content tab.
Step 3. Add a route for upload action.
Create routes.xml under [Vendor]/[Module]/etc/adminhtml. Your file location will be [Vendor]/[Module]/etc/adminhtml/routes.xml.
Add the following code.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:App/etc/routes.xsd">
    <router id="admin">
        <route id="[module]" frontName="[module]">
            <module name="[Vendor]_[Module]" />
        </route>
    </router>
</config>

Step 4. Create Controller for upload action.
Create Upload.php on [Vendor]/[Module]/Controller/Adminhtml/Cms/Heroimage. Your file location will  be [Vendor]/[Module]/Controller/Adminhtml/Cms/Heroimage/Upload.php. 
Add following code.
    

namespace [Vendor]\[Module]\Controller\Adminhtml\Cms\Heroimage;

use Magento\Framework\Controller\ResultFactory;

class Upload extends \Magento\Backend\App\Action
{
    /**
     * Image uploader
     *
     * @var \[Vendor]\[Module]\Model\ImageUploader
     */
    protected $imageUploader;

    /**
     * @param \Magento\Backend\App\Action\Context $context
     * @param \Magento\Catalog\Model\ImageUploader $imageUploader
     */
    public function __construct(
        \Magento\Backend\App\Action\Context $context,
        \Magento\Catalog\Model\ImageUploader $imageUploader
    ) {
        parent::__construct($context);
        $this->imageUploader = $imageUploader;
    }

    /**
     * Upload file controller action
     *
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        try {
            $result = $this->imageUploader->saveFileToTmpDir('your_image_field_name');

            $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);
    }
}
?>

Step 5. Override Save action.
Create di.xml under  [Vendor][Module]\etc\adminhtml. Your file location will be [Vendor][Module]\etc\adminhtml\di.xml
 add following line to override Save Action and DataProvider for cms page.
<?xml version="1.0"?>
<config xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:ObjectManager/etc/config.xsd">
    <preference for="Magento\Cms\Controller\Adminhtml\Page\Save" type="[Vendor]\[Module]\Controller\Adminhtml\Cms\Page\Save" />
    <preference for="Magento\Cms\Model\Page\DataProvider" type="[Vendor]\[Module]\Model\Cms\Page\DataProvider" />
</config>

Step 6. Create Save.php file.
 Create Save.php file under [Vendor][Module]\Controller\Adminhtml\Cms\Page. Your file location will be [Vendor][Module]\Controller\Adminhtml\Cms\Page\Save.php. 
 Add Following code.
    

namespace [Vendor]\[Module]\Controller\Adminhtml\Cms\Page;

use Magento\Backend\App\Action;
use Magento\Cms\Model\Page;
use Magento\Framework\App\Request\DataPersistorInterface;
use Magento\Framework\Exception\LocalizedException;

class Save extends \Magento\Cms\Controller\Adminhtml\Page\Save
{
    /**
     * Authorization level of a basic admin session
     *
     * @see _isAllowed()
     */
    const ADMIN_RESOURCE = 'Magento_Cms::save';

    /**
     * @var PostDataProcessor
     */
    protected $dataProcessor;

    /**
     * @var DataPersistorInterface
     */
    protected $dataPersistor;

    /**
     * Save action
     *
     * @SuppressWarnings(PHPMD.CyclomaticComplexity)
     * @return \Magento\Framework\Controller\ResultInterface
     */
    public function execute()
    {
        $data = $this->getRequest()->getPostValue();

        /** @var \Magento\Backend\Model\View\Result\Redirect $resultRedirect */
        $resultRedirect = $this->resultRedirectFactory->create();
        if ($data) {
            $data = $this->dataProcessor->filter($data);
            if (isset($data['is_active']) && $data['is_active'] === 'true') {
                $data['is_active'] = Page::STATUS_ENABLED;
            }
            if (empty($data['page_id'])) {
                $data['page_id'] = null;
            }

            /** @var \Magento\Cms\Model\Page $model */
            $model = $this->_objectManager->create('Magento\Cms\Model\Page');

            $id = $this->getRequest()->getParam('page_id');
            if ($id) {
                $model->load($id);
            }

            // Add custom image field to data
            if(isset($data['your_image_field_name']) && is_array($data['your_image_field_name'])){
                $data['your_image_field_name']=$data['your_image_field_name'][0]['name'];
            }

            $model->setData($data);

            $this->_eventManager->dispatch(
                'cms_page_prepare_save',
                ['page' => $model, 'request' => $this->getRequest()]
            );

            if (!$this->dataProcessor->validate($data)) {
                return $resultRedirect->setPath('*/*/edit', ['page_id' => $model->getId(), '_current' => true]);
            }

            try {
                $model->save();
                $this->messageManager->addSuccess(__('You saved the page.'));
                $this->dataPersistor->clear('cms_page');
                if ($this->getRequest()->getParam('back')) {
                    return $resultRedirect->setPath('*/*/edit', ['page_id' => $model->getId(), '_current' => true]);
                }
                return $resultRedirect->setPath('*/*/');
            } catch (LocalizedException $e) {
                $this->messageManager->addError($e->getMessage());
            } catch (\Exception $e) {
                $this->messageManager->addException($e, __('Something went wrong while saving the page.'));
            }

            $this->dataPersistor->set('cms_page', $data);
            return $resultRedirect->setPath('*/*/edit', ['page_id' => $this->getRequest()->getParam('page_id')]);
        }
        return $resultRedirect->setPath('*/*/');
    }
}
?>

Step 7.  Create DataProvider.php 
Create DataProvider.php under [Vendor]\Module\Model\Cms\Page. Your File location will be [Vendor]\Module\Model\Cms\Page\DataProvider.php
<?php
/**
 * Copyright © 2013-2017 Magento, Inc. All rights reserved.
 * See COPYING.txt for license details.
 */
namespace [Vendor]\[Module]\Model\Cms\Page;

use Magento\Cms\Model\ResourceModel\Page\CollectionFactory;
use Magento\Framework\App\Request\DataPersistorInterface;

/**
 * Class DataProvider
 */
class DataProvider extends \Magento\Cms\Model\Page\DataProvider
{

    /**
     * Get data
     *
     * @return array
     */
    public function getData()
    {
        if (isset($this->loadedData)) {
            return $this->loadedData;
        }
        $items = $this->collection->getItems();
        /** @var $page \Magento\Cms\Model\Page */
        foreach ($items as $page) {
            $this->loadedData[$page->getId()] = $page->getData();
        }

        $data = $this->dataPersistor->get('cms_page');

        if (!empty($data)) {
            $page = $this->collection->getNewEmptyItem();

            $page->setData($data);
            $this->loadedData[$page->getId()] = $page->getData();
            $this->dataPersistor->clear('cms_page');
        }
        /* For Modify  You custom image field data */
        if(!empty($this->loadedData[$page->getId()]['your_image_field_name'])){
            $objectManager = \Magento\Framework\App\ObjectManager::getInstance();
            $storeManager = $objectManager->get('Magento\Store\Model\StoreManagerInterface');
            $currentStore = $storeManager->getStore();
            $media_url=$currentStore->getBaseUrl(\Magento\Framework\UrlInterface::URL_TYPE_MEDIA);

            $image_name=$this->loadedData[$page->getId()]['your_image_field_name'];
            unset($this->loadedData[$page->getId()]['your_image_field_name']);
            $this->loadedData[$page->getId()]['your_image_field_name'][0]['name']=$image_name;
            $this->loadedData[$page->getId()]['your_image_field_name'][0]['url']=$media_url."cms/hero/tmp/".$image_name;
        }
        return $this->loadedData;
    }
}

Note. Please Replace "your_image_field_name" with your field name. 

