Create a full-fledged Module Step by Step
You could just follow my code to create this module from the scratch. Or you can directly download the compressed tar file and install it and play it. The Link to downlad is at the end of this tutorial.
Part 1: New Module for Front End
A full-fledged Module Creation for Magento 2.
Create a module files: module.xml
#app/code/Jeff/SimpleNews/etc/module.xml
Create a module registration file
#app/code/Jeff/SimpleNews/registration.php
The schema Installation file
#app/code/Jeff/SimpleNews/Setup/InstallSchema.php startSetup(); $tableName = $installer->getTable('tutorial_simplenews'); if($installer->getConnection()->isTableExists($tableName) != true) { $table = $installer->getConnection() ->newTable($tableName) ->addColumn( 'id', Table::TYPE_INTEGER, null, [ 'identity' => true, 'unsigned' => true, 'nullable' => false, 'primary' => true ], 'ID' ) ->addColumn( 'title', Table::TYPE_TEXT, null, ['nullable' => false, 'default' => ''], 'Title' ) ->addColumn( 'summary', Table::TYPE_TEXT, null, ['nullable' => false, 'default' => ''], 'Summary' ) ->addColumn( 'description', Table::TYPE_TEXT, null, ['nullable' => false, 'default' => ''], 'Description' ) ->addColumn( 'created_at', Table::TYPE_DATETIME, null, ['nullable' => false], 'Created At' ) ->addColumn( 'status', Table::TYPE_SMALLINT, null, ['nullable' => false, 'default' => '0'], 'Status' ) ->setComment('News Table') ->setOption('type', 'InnoDB') ->setOption('charset', 'utf8'); $installer->getConnection()->createTable($table); } $installer->endSetup(); } }
Data Upgrade script to insert some data
#app/code/Jeff/SimpleNews/Setup/UpgradeData.php startSetup(); if(version_compare($context->getVersion(), '0.0.2') < 0) { $tableName = $setup->getTable('tutorial_simplenews'); if($setup->getConnection()->isTableExists($tableName) == true ) { $data = [ [ 'title'=>'How to create a simple module', 'summary' => 'The Summary', 'description' => 'The Description', 'created_at' => date('Y-m-d H:i:s'), 'status' => 1 ], [ 'title'=>'How to use model and collection in magento 2', 'summary' => 'The Summary', 'description' => 'The Description', 'created_at' => date('Y-m-d H:i:s'), 'status' => 1 ], [ 'title'=>'Create a module with custom database table', 'summary' => 'The Summary', 'description' => 'The Description', 'created_at' => date('Y-m-d H:i:s'), 'status' => 1 ], ]; foreach($data as $item) { $setup->getConnection()->insert($tableName, $item); } } } $setup->endSetup(); } }
Create Model News for business Logic
#app/Jeff/SimpleNews/Model/News.php _init('Jeff\SimpleNews\Model\ResourceModel\News'); } }
Create Model's ResourceModel to handle real database transaction
#app/code/Jeff/SimpleNews/Model/ResourceModel/News.php _init('tutorial_simplenews', 'id'); } }
Create Model's collection class
#app/code/Jeff/SimpleNews/ModelResourceModel/News/Collection.php _init('Jeff\SimpleNews\Model\News', 'Jeff\SimpleNews\Model\ResourceModel\News'); } }
Setup the frontend route
#app/code/Jeff/SimpleNews/etc/frontend/routes.xml
Create IndexController
#app/code/Jeff/SimpleNews/Controller/Index/Index.php resultPageFactory = $resultPageFactory; $this->_modelNewsFactory = $modelNewsFactory; parent::__construct($context); } public function execute() { //return $this->resultPageFactory->create(); $newsModel = $this->_modelNewsFactory->create(); $item = $newsModel->load(1); var_dump($item->getData()); $newsCollection = $newsModel->getCollection(); var_dump($newsCollection->getData()); } }
Setup Module's backend configuration
#app/code/Jeff/SimpleNews/etc/adminhtml/system.xml tutorial Jeff_SimpleNews::system_config Magento\Config\Model\Config\Source\Yesno Fill head title of news list page at here required-entry Jeff\SimpleNews\Model\System\Config\LatestNews\Position
Create a custom source model
#app/code/Jeff/SimpleNews/Model/System/Config/LatestNews/Position.php __("Left"), self::RIGHT => __('Right'), self::DISABLED => __('Disabled') ]; } }
Create a role for this config section
#app/code/Jeff/SimpleNews/etc/acl.xml
Set some default value for configuration options
#app/code/Jeff/SimpleNews/etc/config.xml 1 Tutorial - Simple News 1
Create a Helper Data class
#app/code/Jeff/SimpleNews/Helper/Data.php _scopeConfig = $scopeConfig; parent::__construct($context); } public function isEnabledInFrontend($store = null) { return $this->_scopeConfig->getValue(self::XML_PATH_ENABLED, ScopeInterface::SCOPE_STORE); } public function getHeadTitle() { return $this->_scopeConfig->getValue(self::XML_PATH_HEAD_TITLE, ScopeInterface::SCOPE_STORE); } public function getLatestNewsBlockPosition() { return $this->_scopeConfig->getValue(self::XML_PATH_LATEST_NEWS, ScopeInterface::SCOPE_STORE); } }
Create Layout file for page handle
#app/code/Jeff/SimpleNews/view/frontend/layout/news_news.xml
Create another layout file by update the previous layout
#app/code/Jeff/SimpleNews/view/frontend/layout/news_index_index.xml
Create Block NewList file
#app/code/Jeff/SimpleNews/Block/NewsList.php _newsFactory = $newsFactory; parent::__construct($context, $data); } protected function _construct() { parent::_construct(); $collection = $this->_newsFactory->create()->getCollection()->setOrder('id', 'DESC'); $this->setCollection($collection); } protected function _prepareLayout(){ parent::_prepareLayout(); $pager = $this->getLayout()->createBlock('Magento\Theme\Block\Html\Pager', 'simplenews.news.list.pager'); $pager->setLimit(5)->setShowAmount(false)->setCollection($this->getCollection()); $this->setChild('pager', $pager); $this->getCollection()->load(); return $this; } public function getPagerHtml() { return $this->getChildHtml('pager'); } }
Create frontend template file list.phtml
#app/code/Jeff/SimpleNews/view/frontend/templates/list.phtml getCollection(); if($newsCollection->getSize() > 0) : ?>
getTitle()?>getSummary() ?>
Create an abstract class by extending Magento Core Action class
#app/code/Jeff/SimpleNews/Controller/News.php _pageFactory = $pageFactory; $this->_dataHelper = $helper; $this->_newsFactory = $newsFactory; parent::__construct($context); } public function dispatch(RequestInterface $request) { if($this->_dataHelper->isEnabledInFrontend()) { $result = parent::dispatch($request); return $result; } else { $this->_forward('noroute'); } } }
Update Index Controller by extends the abstract class 'New.php'
#app/code/Jeff/SimpleNews/Controller/Index/Index.php _pageFactory->create(); $pageFactory->getConfig()->getTitle()->set($this->_dataHelper->getHeadTitle()); //Add breadcrumb $breadcrumbs = $pageFactory->getLayout()->getBlock('breadcrumbs'); $breadcrumbs->addCrumb('home', ['label'=>__('Home'), 'title'=>__('Home'), 'link'=>$this->_url->getUrl('')]); $breadcrumbs->addCrumb('simplenews', ['label'=>__('Simple News'), 'title'=>__('Simple News')]); return $pageFactory; } }
Create a layout file for news detail page
#app/code/Jeff/SimpleNews/view/frontend/layout/news_index_view.xml
Create News view action
#app/code/Jeff/SimpleNews/Controller/Index/View.php getRequest()->getParam('id'); $news = $this->_newsFactory->create()->load($newsId); $this->_objectManager->get('Magento\Framework\Registry')->register('newsData', $news); $pageFactory = $this->_pageFactory->create(); $pageFactory->getConfig()->getTitle()->set($news->getTitle()); $breadcrumbs = $pageFactory->getLayout()->getBlock('breadcrumbs'); $breadcrumbs->addCrumb('home', ['label' => __('Home'), 'title'=>__('Home'), 'link' => $this->_url->getUrl('')]); $breadcrumbs->addCrumb('simplenews', ['label' => __('Simple News'), 'title'=>__('Simple News'), 'link' => $this->_url->getUrl('news')]); $breadcrumbs->addCrumb('news', ['label' => $news->getTitle(), 'title'=>$news->getTitle()]); return $pageFactory; } }
create view news block
#app/code/Jeff/SimpleNews/Block/View.php _coreRegistry = $coreRegistry; parent::__construct($context, $data); } public function getNewsInformation() { return $this->_coreRegistry->registry('newsData'); } }
Create news view template file
#app/code/Jeff/SimpleNews/view/frontend/templates/view.phtml getNewsInformation(); ?> getDescription() ?>
Create CSS file for styling the frontend Page
#app/code/Jeff/SimpleNews/view/frontend/web/css/style.css .simplenews > ul { list-style: none; padding: 0; } .simplenews > ul li { padding: 10px 5px; margin: 0; background-color: #fff; border-bottom: 1px #c4c1bc solid; display: inline-block; width: 100%; } .simplenews > ul li:last-child { border-bottom: none; } .simplenews-list { float: left; position: relative; margin-left: 10px; width: 100%; } .simplenews-list a.news-title { font-weight: bold; } .simplenews-list a.news-title:hover { text-decoration: none; } .block-simplenews .block-title { margin: 0px 0px 20px; } .block-simplenews-heading { font-size: 18px; font-weight: 300; }
Create Latest New Block
#app/code/Jeff/SimpleNews/Block/Latest.php _dataHelper = $dataHelper; $this->_newsFactory = $newsFactory; parent::__construct($context); } //Get five latest news public function getLatestNews() { $collection = $this->_newsFactory->create()->getCollection(); $collection->addFieldToFilter('status', ['eq'=> 1]); $collection->getSelect()->order('id DESC')->limit(5); return $collection; } }
Create a Block for positioning the latest news: Left or Right
Left:Right:#app/code/Jeff/SimpleNews/Block/Latest/Left.php _dataHelper->getLatestNewsBlockPosition(); if($position == Position::LEFT) { $this->setTemplate('Jeff_SimpleNews::latest.phtml'); } } }
#app/code/Jeff/SimpleNews/Block/Latest/Left.php _dataHelper->getLatestNewsBlockPosition(); if($position == Position::RIGHT) { $this->setTemplate('Jeff_SimpleNews::latest.phtml'); } } }
Create the template file for Latest News
#app/code/Jeff/SimpleNews/view/frontend/template/latest.phtml getLatestnews(); if($latestNews->getSize() > 0) : ?>
Frontend view for the module
Part 2: New Module for Back End
Create the menu for Magento backend
#app/code/Jeff/SimpleNews/etc/adminhtml/menu.xml
Create backend route file
#app/code/Jeff/SimpleNews/etc/adminhtml/routes.xml
Update the acl.xml to add more roles
#app/code/Jeff/SimpleNews/etc/acl.xml
Create layout for grid
#app/code/Jeff/SimpleNews/view/adminhtml/layout/simplenews_news_grid_block.xml newsGrid Jeff\SimpleNews\Model\ResourceModel\News\Collection id desc true true 1 id news - Delete
- */*/massDelete
- Are your sure you want to delete?
- */*/edit
- getId
ID number id id Title title Summary summary Status status options action Action action getId false false stores true - Edit
- */*/edit
- id
col-actions col-actions
Create layout for Grid Container
#app/code/Jeff/SimpleNews/view/adminhtml/layout/simplenews_news_index.xml
Create layout for ajax load
#app/code/Jeff/SimpleNews/view/adminhtml/layout/simplenews_news_grid.xml
Create news status option file
#app/code/Jeff/SimpleNews/Model/System/Config/Status.php __('Enabled'), self::DISABLED => __('Disabled') ]; return $options; } }
Create News Block for backend
#app/code/Jeff/SimpleNews/Block/Adminhtml/News.php _controller = 'adminhtml_news'; //controller name $this->_blockGroup = 'Jeff_SimpleNews'; //Module name $this->_headerText = __('Manage News'); $this->_addButtonLabel = __('Add News'); parent::_construct(); } }
Create Grid block file for Ajax load
#app/code/Jeff/SimpleNews/Block/Adminhtml/News/Grid.php
Create a backend controller file for child action class to extend
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News.php _coreRegistry = $coreRegistry; $this->_resultPageFactory = $resultPageFactory; $this->_newsFactory = $newsFactory; parent::__construct($context); } protected function _isAllowed() { return $this->_authorization->isAllowed('Jeff_SimpleNews::manage_news'); } }
Create Backend Action file Index.php
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/Index.php getRequest()->getQuery('ajax')) { $this->_forward('grid'); return; } $resultPage = $this->_resultPageFactory->create(); $resultPage->setActiveMenu('Jeff_SimpleNews::main_menu'); $resultPage->getConfig()->getTitle()->prepend(__('Jeff Simple News')); return $resultPage; } }
Create another Action for ajax
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/Grid.php _resultPageFactory->create(); } }
Create layout file simplenews_news_edit.xml for edit form
#app/code/Jeff/SimpleNews/view/adminhtml/layout/simplenews_news_edit.xml
Create the layout for create form
#app/code/Jeff/SimpleNews/view/adminhtml/layout/simplenews_news_create.xml
Create a form container block
#app/code/Jeff/Simplenews/Block/Adminhtml/News/Edit.php _coreRegistry = $registry; parent::__construct($context, $data); } protected function _construct() { $this->_objectId = 'id'; $this->_controller = 'adminhtml_news'; $this->_blockGroup = 'Jeff_SimpleNews'; parent::_construct(); $this->buttonList->update('delete', 'label', __('Delete')); } public function getHeaderText() { $newsRegistry = $this->_coreRegistry->registry('simplenews_news'); if($newsRegistry->getId()) { $newsTitle = $this->escapeHtml($newsRegistry->getTitle()); return __("Edit News '%1'", $newsTitle); } else { return __('Add News'); } } protected function _prepareLayout() { $this->_formScripts[] = " function toggleEditor() { if(tinyMCE.getInstanceById('post_content') == null) { tinyMCE.execCommand('mceAddControl', false, 'post_content'); } else { tinyMCE.execCommand('mceRemoveControl', false, 'post_content'); } }; "; return parent::_prepareLayout(); } }
create a block for the left-side tabs
#app/code/Jeff/SimpleNews/Block/Adminhtml/News/Edit/Tabs.php setId('news_edit_tabs'); $this->setDestElementId('edit_form'); $this->setTitle(__('News Information')); } protected function _beforeToHtml() { $this->addTab('news_info', [ 'label' => __('General'), 'title' => __('General'), 'content' => $this->getLayout()->createBlock('Jeff\SimpleNews\Block\Adminhtml\News\Edit\Tab\Info')->toHtml(), 'active' => true ] ); return parent::_beforeToHtml(); } }
Create a block for Form information
#app/cope/Jeff/SimpleNews/Block/Adminhtml/News/Edit/Form.php _formFactory->create( [ 'data' => [ 'id' => 'edit_form', 'action' => $this->getData('action'), 'method' => 'post' ] ] ); $form->setUseContainer(true); $this->setForm($form); return parent::_prepareForm(); } }
Create a block to declare the fields for the edit form
#app/code/Jeff/SimpleNews/Block/Adminhmtl/News/Edit/Tab/Info.php _wysiwygConfig = $wysiwygConfig; $this->_newsStatus = $newsStatus; parent::__construct($context, $registry, $formFactory, $data); } protected function _prepareForm() { $model = $this->_coreRegistry->registry('simplenews_news'); $form = $this->_formFactory->create(); $form->setHtmlIdPrefix('news_'); $form->setFieldNameSuffix('news'); $fieldset = $form->addFieldset('base_fieldset', ['legend'=>__('General')]); if($model->getId()) { $fieldset->addField('id', 'hidden', ['name'=>'id']); } $fieldset->addField('title', 'text', ['name'=>'title', 'label' => __('Title'), 'required' => true ]); $fieldset->addField('status', 'select', ['name'=>'status', 'label' => __('Status'), 'options' => $this->_newsStatus->toOptionArray() ]); $fieldset->addField('summary', 'textarea', ['name'=>'summary', 'label' => __('Summary'), 'required' => true ]); $wysiwygConfig = $this->_wysiwygConfig->getConfig(); $fieldset->addField('description', 'editor', ['name'=>'description', 'label' => __('Description'), 'required' => true, 'config' => $wysiwygConfig ]); $data = $model->getData(); $form->setValues($data); $this->setForm($form); return parent::_prepareForm(); } public function getTabLabel() { return __('News Info'); } public function getTabTitle() { return __('News Info'); } public function canShowTab() { return true; } public function isHidden() { return false; } }
Create a controller action for create a new News
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/NewAction.php _forward('edit'); } }
Create Edit Action for the Edit form
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/Edit.php getRequest()->getParam('id'); $model = $this->_newsFactory->create(); if($newsId) { $model->load($newsId); //just load the id to get the object if(!$model->getId()) { $this->messageManager->addError(__('This news no longer exists.')); $this->_redirect('*/*/'); return; } } $data = $this->_session->getNewsData(true); if(!empty($data)) { $model->setData($data); } $this->_coreRegistry->register('simplenews_news', $model); $resultPage = $this->_resultPageFactory->create(); $resultPage->setActiveMenu('Jeff_SimpleNews::main_menu'); $resultPage->getConfig()->getTitle()->prepend(__('Simple News')); return $resultPage; } }
A Save Action for the edit form
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/Save.php getRequest()->getPost(); if($isPost) { $newsModel = $this->_newsFactory->create(); $newsId = $this->getRequest()->getParam('id'); if($newsId) { $newsModel->load($newsId); } $formData = $this->getRequest()->getParam('news'); $newsModel->setData($formData); try { $newsModel->save(); $this->messageManager->addSuccess(__('The news has been saved.')); /** Check if 'Save and Continue' */ if($this->getRequest()->getParam('back')) { $this->_redirect('*/*/edit', ['id' => $newsModel->getId(), '_current'=>true]); return; } //go to grid page $this->_redirect('*/*/'); return; } catch(\Exception $e) { $this->messageManager->addError($e->getMessage()); } //When there are some errors the following codes will execute $this->_getSession()->setFormData($formData); $this->_redirect('*/*/edit', ['id' => $newsId]); } } }
Delete Action for the edit Form
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/Delete.php getRequest()->getParam('id'); if($newsId) { $newsModel = $this->_newsFactory->create(); $newsModel->load($newsId); //Check if this news exists if(!$newsModel->getId()) { $this->messageManager->addError(__('This news no longer exists.')); } else { try { $newsModel->delete(); $this->messageManager->addSuccess(__('The news has been deleted.')); $this->_redirect('*/*/'); return; } catch(\Exception $e) { $this->messageManager->addError($e->getMessage()); $this->_redirect('*/*/edit', ['id' => $newsModel->getId()]); } } } } }
The mass delete action the grid list
#app/code/Jeff/SimpleNews/Controller/Adminhtml/News/MassDelete.php getRequest()->getParam('news'); foreach($newsIds as $newsId) { try{ $newsModel = $this->_newFactory->create(); $newsModel->load($newsId)->delete(); } catch(\Exception $e) { $this->messageManager->addError($e->getMessage()); } } if(count($newsIds)) { $this->messageManager->addSuccess(__('A total of 1% records were deleted.', count($newsIds))); } $this->_redirect('*/*/index'); } }
This is the end of this tutorial. You now have a full functional module and you can convert this module into blog module for Magento 2 by a little bit tweak.
The screen shots for the backend as following:
Backend Menu and Grid List
Create new News or Edit a News
The code can be downloaded at GitHub