I have done it like this , hope it will help you , i have extracted the script from my full module just to explain the flow of adding form and saving form data to database.
It is bit lengthy , but tried to explain in detail
For adding custom tab to customer dashboard
created a file view/frontend/layout/customer_account.xml with script
<?xml version="1.0"?>
<!--
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="2columns-left" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd" label="Customer My Account (All Pages)" design_abstraction="custom">
<body>
<referenceBlock name="customer_account_navigation">
<block class="Magento\Framework\View\Element\Html\Link\Current" name="customer-account-navigation-customtab-link">
<arguments>
<argument name="label" xsi:type="string" translate="true">Profile</argument>
<argument name="path" xsi:type="string">customtab/profile</argument>
</arguments>
</block>
</referenceBlock>
</body>
</page>
Then created the controller Controller/Profile/Index.php that will work for path customtab/profile with script
namespace Sample\Customtab\Controller\Profile;
class Index extends \Sample\Customtab\Controller\Profile
{
/**
* Managing newsletter subscription page
*
* @return void
*/
public function execute()
{
$this->_view->loadLayout();
/*if ($block = $this->_view->getLayout()->getBlock('customer_newsletter')) {
$block->setRefererUrl($this->_redirect->getRefererUrl());
}*/
$this->_view->getPage()->getConfig()->getTitle()->set(__('Custom Profile'));
$this->_view->renderLayout();
}
}
Now created the layout file at view/frontend/layout/customtab_profile_index.xml with script
<?xml version="1.0"?>
<!--
/**
* Copyright © 2015 Magento. All rights reserved.
* See COPYING.txt for license details.
*/
-->
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<update handle="customer_account"/>
<body>
<referenceContainer name="content">
<block class="Sample\Customtab\Block\Store\Profile" name="customtab.store.profile" template="Sample_Customtab::store/profile.phtml" />
</referenceContainer>
</body>
</page>
Now i have to create the block Block/Store/Profile.php
namespace Sample\Customtab\Block\Store;
/**
* Sales order history block
*/
class Profile extends \Magento\Framework\View\Element\Template
{
/**
* @var string
*/
//protected $_template = 'order/history.phtml';
/**
* @var \Magento\Sales\Model\ResourceModel\Order\CollectionFactory
*/
protected $collectionFactory;
/**
* @var \Magento\Customer\Model\Session
*/
protected $_customerSession;
/**
* @var \Magento\Sales\Model\Order\Config
*/
protected $_orderConfig;
/** @var \Magento\Sales\Model\ResourceModel\Order\Collection */
protected $orders;
/**
* @param \Magento\Framework\View\Element\Template\Context $context
* @param \Magento\Sales\Model\ResourceModel\Order\CollectionFactory $orderCollectionFactory
* @param \Magento\Customer\Model\Session $customerSession
* @param \Magento\Sales\Model\Order\Config $orderConfig
* @param array $data
*/
public function __construct(
\Magento\Framework\View\Element\Template\Context $context,
\Sample\Customtab\Model\ResourceModel\Stores\CollectionFactory $collectionFactory,
\Magento\Customer\Model\Session $customerSession,
array $data = []
) {
$this->collectionFactory = $collectionFactory;
$this->_customerSession = $customerSession;
parent::__construct($context, $data);
}
/**
* @return void
*/
protected function _construct()
{
parent::_construct();
$this->pageConfig->getTitle()->set(__('Store Profile'));
$customerId = $this->_customerSession->getCustomerId();
if($customerId)
{
$profiledata = $this->collectionFactory->create()->addFieldToSelect('*')
->addFieldToFilter('customer_id',$customerId);
foreach($profiledata as $profile)
{
$this->setProfileData($profile);
}
}
}
}
And now the called phtml file view/frontend/templates/store/profile.phtml
$storeProfile = $block->getProfileData();
And in the form you have to put form action like
action="<?php echo $this->getUrl('customtab/profile/save'); ?>"
I have just place the code to fetch the data , in this phtml file you can put your html code with your required fields.
Now for \Sample\Customtab\Model\ResourceModel\Stores\CollectionFactory used in block we have to create collection for it
Create a model Model/Stores.php with
namespace Sample\Customtab\Model;
/**
* Megamenutab megamenu model
*/
class Stores extends \Magento\Framework\Model\AbstractModel
{
/**
* @param \Magento\Framework\Model\Context $context
* @param \Magento\Framework\Registry $registry
* @param \Magento\Framework\Model\ResourceModel\AbstractResource $resource
* @param \Magento\Framework\Data\Collection\Db $resourceCollection
* @param array $data
*/
public function __construct(
\Magento\Framework\Model\Context $context,
\Magento\Framework\Registry $registry,
\Magento\Framework\Model\ResourceModel\AbstractResource $resource = null,
\Magento\Framework\Data\Collection\AbstractDb $resourceCollection = null,
array $data = []
) {
parent::__construct($context, $registry, $resource, $resourceCollection, $data);
}
/**
* @return void
*/
public function _construct()
{
$this->_init('Sample\Customtab\Model\ResourceModel\Stores');
}
}
The ResourceModel Model/ResourceModel/Stores.php with
namespace Sample\Customtab\Model\ResourceModel;
class Stores extends \Magento\Framework\Model\ResourceModel\Db\AbstractDb
{
public function _construct()
{
$this->_init('yourtablename', 'id'); // with id primery key
}
}
Then Collection Model Model/ResourceModel/Stores/Collection.php with
namespace Sample\Customtab\Model\ResourceModel\Stores;
class Collection extends \Magento\Framework\Model\ResourceModel\Db\Collection\AbstractCollection
{
public function _construct()
{
$this->_init('Sample\Customtab\Model\Stores', 'Sample\Customtab\Model\ResourceModel\Stores');
}
}
Now have to create abstract class for save profile controller Controller/Profile.php
namespace Sample\Customtab\Controller;
use Magento\Framework\App\RequestInterface;
abstract class Profile extends \Magento\Framework\App\Action\Action
{
protected $_customerSession;
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Customer\Model\Session $customerSession
) {
parent::__construct($context);
$this->_customerSession = $customerSession;
}
public function dispatch(RequestInterface $request)
{
if (!$this->_customerSession->authenticate()) {
$this->_actionFlag->set('', 'no-dispatch', true);
}
return parent::dispatch($request);
}
}
Now have to create profile save controller that will work on profile save form action Controller/Profile/Save.php
namespace Sample\Customtab\Controller\Profile;
use Magento\Customer\Api\CustomerRepositoryInterface as CustomerRepository;
class Save extends \Sample\Customtab\Controller\Profile
{
protected $formKeyValidator;
protected $storeManager;
protected $customerRepository;
protected $subscriberFactory;
public function __construct(
\Magento\Framework\App\Action\Context $context,
\Magento\Customer\Model\Session $customerSession,
\Magento\Framework\Data\Form\FormKey\Validator $formKeyValidator,
\Magento\Store\Model\StoreManagerInterface $storeManager,
CustomerRepository $customerRepository,
\Magento\Newsletter\Model\SubscriberFactory $subscriberFactory
) {
$this->storeManager = $storeManager;
$this->formKeyValidator = $formKeyValidator;
$this->customerRepository = $customerRepository;
$this->subscriberFactory = $subscriberFactory;
parent::__construct($context, $customerSession);
}
/**
* Save newsletter subscription preference action
*
* @return void|null
*/
public function execute()
{
if (!$this->formKeyValidator->validate($this->getRequest())) {
return $this->_redirect('customer/account/');
}
$customerId = $this->_customerSession->getCustomerId();
if ($customerId === null) {
$this->messageManager->addError(__('Something went wrong while saving your subscription.'));
} else {
try {
$data = $this->getRequest()->getParams();
$model = $this->_objectManager->create('Sample\Customtab\Model\Stores');
$data['customer_id'] = $customerId; // customer_id is one of the field from custom database table
$id = $this->getRequest()->getParam('id');
if ($id) {
$model->load($id);
}
$model->setData($data);
$model->save();
$this->messageManager->addSuccess(__('Profile has beem saved successfully.'));
$this->_redirect('*/*/');
return;
} catch (\Exception $e) {
$this->messageManager->addError(__('Something went wrong while saving your profile.'));
}
}
$this->_redirect('customer/account/');
}
}
Hope i have included all the things. And it will work for you.
If you followed this tutorial to create new checkout step, so you can continue like these files below
Layout file checkout_index_index.xml
<?xml version="1.0"?>
<page xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" layout="1column" xsi:noNamespaceSchemaLocation="urn:magento:framework:View/Layout/etc/page_configuration.xsd">
<body>
<referenceBlock name="checkout.root">
<arguments>
<argument name="jsLayout" xsi:type="array">
<item name="components" xsi:type="array">
<item name="checkout" xsi:type="array">
<item name="children" xsi:type="array">
<item name="steps" xsi:type="array">
<item name="children" xsi:type="array">
<!-- The new step you add -->
<item name="shipping-detail" xsi:type="array">
<item name="component" xsi:type="string">Walish_StackFarm/js/view/shipping-detail</item>
<item name="sortOrder" xsi:type="string">2</item>
<item name="children" xsi:type="array">
<!--Your shipping information content -->
<item name="shipping-information" xsi:type="array">
<item name="component" xsi:type="string">Magento_Checkout/js/view/shipping-information</item>
<item name="config" xsi:type="array">
<item name="deps" xsi:type="string">checkout.steps.shipping-step.shippingAddress</item>
</item>
<item name="displayArea" xsi:type="string">shipping-information</item>
<item name="children" xsi:type="array">
<item name="ship-to" xsi:type="array">
<item name="component" xsi:type="string">Magento_Checkout/js/view/shipping-information/list</item>
<item name="displayArea" xsi:type="string">ship-to</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
<item name="sidebar" xsi:type="array">
<item name="children" xsi:type="array">
<!--Disable sidebar component-->
<item name="shipping-information" xsi:type="array">
<item name="config" xsi:type="array">
<item name="componentDisabled" xsi:type="boolean">true</item>
</item>
</item>
</item>
</item>
</item>
</item>
</item>
</argument>
</arguments>
</referenceBlock>
</body>
</page>
Template file view/frontend/web/template/shipping-detail.html
<li id="shipping_detail" data-bind="fadeVisible: isVisible">
<div class="step-title" data-bind="i18n: 'Shipping Detail'" data-role="title"></div>
<div id="checkout-step-title"
class="step-content"
data-role="content">
<form data-bind="submit: navigateToNextStep" novalidate="novalidate">
<!--Put your shipping information block content here-->
<div class="opc-block-shipping-information">
<!-- ko foreach: getRegion('shipping-information') -->
<!-- ko template: getTemplate() --><!-- /ko -->
<!--/ko-->
</div>
<div class="actions-toolbar">
<div class="primary">
<button data-role="opc-continue" type="submit" class="button action continue primary">
<span><!-- ko i18n: 'Next'--><!-- /ko --></span>
</button>
</div>
</div>
</form>
</div>
</li>
And result will be like this
Best Answer
Hope you doing well.
I'm also facing this issue but after some time have solved this.
Follow mentioned steps.
create file at view/frontend/layout/customer_account.xml.
Now create Phtml file for customer-account-navigation-delimiter-4 block.
view/frontend/templates/account/navigation-delimiter.phtml
Hope you have the result you want. :-)