diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..bdb0cab --- /dev/null +++ b/.gitattributes @@ -0,0 +1,17 @@ +# Auto detect text files and perform LF normalization +* text=auto + +# Custom for Visual Studio +*.cs diff=csharp + +# Standard to msysgit +*.doc diff=astextplain +*.DOC diff=astextplain +*.docx diff=astextplain +*.DOCX diff=astextplain +*.dot diff=astextplain +*.DOT diff=astextplain +*.pdf diff=astextplain +*.PDF diff=astextplain +*.rtf diff=astextplain +*.RTF diff=astextplain diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1b3ae7d --- /dev/null +++ b/.gitignore @@ -0,0 +1,25 @@ +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +nbproject +vendor +composer.phar +composer.lock + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +.idea/ diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..4697d26 --- /dev/null +++ b/composer.json @@ -0,0 +1,34 @@ +{ + "name": "hoaaah/yii2-ajaxcrud-bs4", + "description": "Gii CRUD template for Single Page Ajax Administration for yii2", + "type": "yii2-extension", + "keywords": ["yii2","extension","ajax","crud","gii","template","database"], + "license": "Apache-2.0", + "authors": [ + { + "name": "John Martin", + "email": "john.itvn@gmail.com", + "homepage": "https://github.com/johnitvn?tab=repositories" + } + ], + "support": { + "issues": "https://github.com/johnitvn/yii2-ajaxcrud/issues?state=open", + "source": "https://github.com/johnitvn/yii2-ajaxcrud" + }, + "require": { + "yiisoft/yii2": "*", + "yiisoft/yii2-gii": "*", + "yiisoft/yii2-bootstrap": "*", + "kartik-v/yii2-grid": "^3.0.4", + "kartik-v/yii2-mpdf": "^1.0.0", + "kartik-v/yii2-editable": "^1.7.3" + }, + "autoload": { + "psr-4": { + "hoaaah\\ajaxcrud\\": "src/" + } + }, + "extra": { + "bootstrap": "hoaaah\\ajaxcrud\\Bootstrap" + } +} diff --git a/src/Bootstrap.php b/src/Bootstrap.php new file mode 100644 index 0000000..217277f --- /dev/null +++ b/src/Bootstrap.php @@ -0,0 +1,30 @@ + + * @since 1.0 + */ +class Bootstrap implements BootstrapInterface { + + /** + * Bootstrap method to be called during application bootstrap stage. + * + * @param Application $app the application currently running + */ + public function bootstrap($app) { + Yii::setAlias("@ajaxcrud", __DIR__); + Yii::setAlias("@hoaaah/ajaxcrud", __DIR__); + if ($app->hasModule('gii')) { + if (!isset($app->getModule('gii')->generators['ajaxcrud'])) { + $app->getModule('gii')->generators['ajaxcrud'] = 'hoaaah\ajaxcrud\generators\Generator'; + } + } + } + +} diff --git a/src/BulkButtonWidget.php b/src/BulkButtonWidget.php new file mode 100644 index 0000000..d10c2eb --- /dev/null +++ b/src/BulkButtonWidget.php @@ -0,0 +1,24 @@ +'. + '  With selected  '. + $this->buttons. + ''; + return $content; + } +} +?> diff --git a/src/CrudAsset.php b/src/CrudAsset.php new file mode 100644 index 0000000..4abc467 --- /dev/null +++ b/src/CrudAsset.php @@ -0,0 +1,42 @@ + + * @since 1.0 + */ +class CrudAsset extends AssetBundle +{ + public $sourcePath = '@ajaxcrud/assets'; + +// public $publishOptions = [ +// 'forceCopy' => true, +// ]; + + public $css = [ + 'ajaxcrud.css' + ]; + + public $depends = [ + 'yii\web\YiiAsset', + 'yii\bootstrap\BootstrapAsset', + 'yii\bootstrap\BootstrapPluginAsset', + 'kartik\grid\GridViewAsset', + ]; + + public function init() { + // In dev mode use non-minified javascripts + $this->js = YII_DEBUG ? [ + 'ModalRemote.js', + 'ajaxcrud.js', + ]:[ + 'ModalRemote.min.js', + 'ajaxcrud.min.js', + ]; + + parent::init(); + } +} diff --git a/src/assets/ModalRemote.js b/src/assets/ModalRemote.js new file mode 100644 index 0000000..0b7b616 --- /dev/null +++ b/src/assets/ModalRemote.js @@ -0,0 +1,391 @@ +/*! + * Modal Remote + * ================================= + * Use for johnitvn/yii2-ajaxcrud extension + * @author John Martin john.itvn@gmail.com + */ +(function ($) { + $.fn.hasAttr = function (name) { + return this.attr(name) !== undefined; + }; +}(jQuery)); + + +function ModalRemote(modalId) { + + this.defaults = { + okLabel: "OK", + executeLabel: "Execute", + cancelLabel: "Cancel", + loadingTitle: "Loading" + }; + + this.modal = $(modalId); + + this.dialog = $(modalId).find('.modal-dialog'); + + this.header = $(modalId).find('.modal-header'); + + this.content = $(modalId).find('.modal-body'); + + this.footer = $(modalId).find('.modal-footer'); + + this.loadingContent = '
'; + + + /** + * Show the modal + */ + this.show = function () { + this.clear(); + $(this.modal).modal('show'); + }; + + /** + * Hide the modal + */ + this.hide = function () { + $(this.modal).modal('hide'); + }; + + /** + * Toogle show/hide modal + */ + this.toggle = function () { + $(this.modal).modal('toggle'); + }; + + /** + * Clear modal + */ + this.clear = function () { + $(this.modal).find('.modal-title').remove(); + $(this.content).html(""); + $(this.footer).html(""); + }; + + /** + * Set size of modal + * @param {string} size large/normal/small + */ + this.setSize = function (size) { + $(this.dialog).removeClass('modal-lg'); + $(this.dialog).removeClass('modal-sm'); + if (size == 'large') + $(this.dialog).addClass('modal-lg'); + else if (size == 'small') + $(this.dialog).addClass('modal-sm'); + else if (size !== 'normal') + console.warn("Undefined size " + size); + }; + + /** + * Set modal header + * @param {string} content The content of modal header + */ + this.setHeader = function (content) { + $(this.header).html(content); + }; + + /** + * Set modal content + * @param {string} content The content of modal content + */ + this.setContent = function (content) { + $(this.content).html(content); + }; + + /** + * Set modal footer + * @param {string} content The content of modal footer + */ + this.setFooter = function (content) { + $(this.footer).html(content); + }; + + /** + * Set modal footer + * @param {string} title The title of modal + */ + this.setTitle = function (title) { + // remove old title + $(this.header).find('h4.modal-title').remove(); + // add new title + $(this.header).append(''); + }; + + /** + * Hide close button + */ + this.hidenCloseButton = function () { + $(this.header).find('button.close').hide(); + }; + + /** + * Show close button + */ + this.showCloseButton = function () { + $(this.header).find('button.close').show(); + }; + + /** + * Show loading state in modal + */ + this.displayLoading = function () { + this.setContent(this.loadingContent); + this.setTitle(this.defaults.loadingTitle); + }; + + /** + * Add button to footer + * @param string label The label of button + * @param string classes The class of button + * @param callable callback the callback when button click + */ + this.addFooterButton = function (label, type, classes, callback) { + buttonElm = document.createElement('button'); + buttonElm.setAttribute('type', type === null ? 'button' : type); + buttonElm.setAttribute('class', classes === null ? 'btn btn-primary' : classes); + buttonElm.innerHTML = label; + var instance = this; + $(this.footer).append(buttonElm); + if (callback !== null) { + $(buttonElm).click(function (event) { + callback.call(instance, this, event); + }); + } + }; + + /** + * Send ajax request and wraper response to modal + * @param {string} url The url of request + * @param {string} method The method of request + * @param {object}data of request + */ + this.doRemote = function (url, method, data) { + var instance = this; + $.ajax({ + url: url, + method: method, + data: data, + async: false, + beforeSend: function () { + beforeRemoteRequest.call(instance); + }, + error: function (response) { + errorRemoteResponse.call(instance, response); + }, + success: function (response) { + successRemoteResponse.call(instance, response); + }, + contentType: false, + cache: false, + processData: false + }); + }; + + /** + * Before send request process + * - Ensure clear and show modal + * - Show loading state in modal + */ + function beforeRemoteRequest() { + this.show(); + this.displayLoading(); + } + + + /** + * When remote sends error response + * @param {string} response + */ + function errorRemoteResponse(response) { + this.setTitle(response.status + response.statusText); + this.setContent(response.responseText); + this.addFooterButton('Close', 'button', 'btn btn-default', function (button, event) { + this.hide(); + }) + } + + /** + * When remote sends success response + * @param {string} response + */ + function successRemoteResponse(response) { + + // Reload datatable if response contain forceReload field + if (response.forceReload !== undefined && response.forceReload) { + if (response.forceReload == 'true') { + // Backwards compatible reload of fixed crud-datatable-pjax + $.pjax.reload({container: '#crud-datatable-pjax'}); + } else { + $.pjax.reload({container: response.forceReload}); + } + } + + // Close modal if response contains forceClose field + if (response.forceClose !== undefined && response.forceClose) { + this.hide(); + return; + } + + if (response.size !== undefined) + this.setSize(response.size); + + if (response.title !== undefined) + this.setTitle(response.title); + + if (response.content !== undefined) + this.setContent(response.content); + + if (response.footer !== undefined) + this.setFooter(response.footer); + + if ($(this.content).find("form")[0] !== undefined) { + this.setupFormSubmit( + $(this.content).find("form")[0], + $(this.footer).find('[type="submit"]')[0] + ); + } + } + + /** + * Prepare submit button when modal has form + * @param {string} modalForm + * @param {object} modalFormSubmitBtn + */ + this.setupFormSubmit = function (modalForm, modalFormSubmitBtn) { + + if (modalFormSubmitBtn === undefined) { + // If submit button not found throw warning message + console.warn('Modal has form but does not have a submit button'); + } else { + var instance = this; + + // Submit form when user clicks submit button + $(modalFormSubmitBtn).click(function (e) { + var data; + + // Test if browser supports FormData which handles uploads + if (window.FormData) { + data = new FormData($(modalForm)[0]); + } else { + // Fallback to serialize + data = $(modalForm).serializeArray(); + } + + instance.doRemote( + $(modalForm).attr('action'), + $(modalForm).hasAttr('method') ? $(modalForm).attr('method') : 'GET', + data + ); + }); + } + }; + + /** + * Show the confirm dialog + * @param {string} title The title of modal + * @param {string} message The message for ask user + * @param {string} okLabel The label of ok button + * @param {string} cancelLabel The class of cancel button + * @param {string} size The size of the modal + * @param {string} dataUrl Where to post + * @param {string} dataRequestMethod POST or GET + * @param {number[]} selectedIds + */ + this.confirmModal = function (title, message, okLabel, cancelLabel, size, dataUrl, dataRequestMethod, selectedIds) { + this.show(); + this.setSize(size); + + if (title !== undefined) { + this.setTitle(title); + } + // Add form for user input if required + this.setContent('
'+message); + + var instance = this; + if (okLabel !== false) { + this.addFooterButton( + okLabel === undefined ? this.defaults.okLabel : okLabel, + 'submit', + 'btn btn-primary', + function (e) { + var data; + + // Test if browser supports FormData which handles uploads + if (window.FormData) { + data = new FormData($('#ModalRemoteConfirmForm')[0]); + if (typeof selectedIds !== 'undefined' && selectedIds) + data.append('pks', selectedIds.join()); + } else { + // Fallback to serialize + data = $('#ModalRemoteConfirmForm'); + if (typeof selectedIds !== 'undefined' && selectedIds) + data.pks = selectedIds; + data = data.serializeArray(); + } + + instance.doRemote( + dataUrl, + dataRequestMethod, + data + ); + } + ); + } + + this.addFooterButton( + cancelLabel === undefined ? this.defaults.cancelLabel : cancelLabel, + 'button', + 'btn btn-default pull-left', + function (e) { + this.hide(); + } + ); + + } + + /** + * Open the modal + * HTML data attributes for use in local confirm + * - href/data-url (If href not set will get data-url) + * - data-request-method (string GET/POST) + * - data-confirm-ok (string OK button text) + * - data-confirm-cancel (string cancel button text) + * - data-confirm-title (string title of modal box) + * - data-confirm-message (string message in modal box) + * - data-modal-size (string small/normal/large) + * Attributes for remote response (json) + * - forceReload (string reloads a pjax ID) + * - forceClose (boolean remote close modal) + * - size (string small/normal/large) + * - title (string/html title of modal box) + * - content (string/html content in modal box) + * - footer (string/html footer of modal box) + * @params {elm} + */ + this.open = function (elm, bulkData) { + /** + * Show either a local confirm modal or get modal content through ajax + */ + if ($(elm).hasAttr('data-confirm-title') || $(elm).hasAttr('data-confirm-message')) { + this.confirmModal ( + $(elm).attr('data-confirm-title'), + $(elm).attr('data-confirm-message'), + $(elm).attr('data-confirm-alert') ? false : $(elm).attr('data-confirm-ok'), + $(elm).attr('data-confirm-cancel'), + $(elm).hasAttr('data-modal-size') ? $(elm).attr('data-modal-size') : 'normal', + $(elm).hasAttr('href') ? $(elm).attr('href') : $(elm).attr('data-url'), + $(elm).hasAttr('data-request-method') ? $(elm).attr('data-request-method') : 'GET', + bulkData + ) + } else { + this.doRemote( + $(elm).hasAttr('href') ? $(elm).attr('href') : $(elm).attr('data-url'), + $(elm).hasAttr('data-request-method') ? $(elm).attr('data-request-method') : 'GET', + bulkData + ); + } + } +} // End of Object diff --git a/src/assets/ModalRemote.min.js b/src/assets/ModalRemote.min.js new file mode 100644 index 0000000..1acddb2 --- /dev/null +++ b/src/assets/ModalRemote.min.js @@ -0,0 +1 @@ +function ModalRemote(t){function o(){this.show(),this.displayLoading()}function e(t){this.setTitle(t.status+t.statusText),this.setContent(t.responseText),this.addFooterButton("Close","button","btn btn-default",function(t,o){this.hide()})}function i(t){return void 0!==t.forceReload&&t.forceReload&&("true"==t.forceReload?$.pjax.reload({container:"#crud-datatable-pjax"}):$.pjax.reload({container:t.forceReload})),void 0!==t.forceClose&&t.forceClose?void this.hide():(void 0!==t.size&&this.setSize(t.size),void 0!==t.title&&this.setTitle(t.title),void 0!==t.content&&this.setContent(t.content),void 0!==t.footer&&this.setFooter(t.footer),void(void 0!==$(this.content).find("form")[0]&&this.setupFormSubmit($(this.content).find("form")[0],$(this.footer).find('[type="submit"]')[0])))}this.defaults={okLabel:"OK",executeLabel:"Execute",cancelLabel:"Cancel",loadingTitle:"Loading"},this.modal=$(t),this.dialog=$(t).find(".modal-dialog"),this.header=$(t).find(".modal-header"),this.content=$(t).find(".modal-body"),this.footer=$(t).find(".modal-footer"),this.loadingContent='
',this.show=function(){this.clear(),$(this.modal).modal("show")},this.hide=function(){$(this.modal).modal("hide")},this.toggle=function(){$(this.modal).modal("toggle")},this.clear=function(){$(this.modal).find(".modal-title").remove(),$(this.content).html(""),$(this.footer).html("")},this.setSize=function(t){$(this.dialog).removeClass("modal-lg"),$(this.dialog).removeClass("modal-sm"),"large"==t?$(this.dialog).addClass("modal-lg"):"small"==t?$(this.dialog).addClass("modal-sm"):"normal"!==t&&console.warn("Undefined size "+t)},this.setHeader=function(t){$(this.header).html(t)},this.setContent=function(t){$(this.content).html(t)},this.setFooter=function(t){$(this.footer).html(t)},this.setTitle=function(t){$(this.header).find("h4.modal-title").remove(),$(this.header).append('")},this.hidenCloseButton=function(){$(this.header).find("button.close").hide()},this.showCloseButton=function(){$(this.header).find("button.close").show()},this.displayLoading=function(){this.setContent(this.loadingContent),this.setTitle(this.defaults.loadingTitle)},this.addFooterButton=function(t,o,e,i){buttonElm=document.createElement("button"),buttonElm.setAttribute("type",null===o?"button":o),buttonElm.setAttribute("class",null===e?"btn btn-primary":e),buttonElm.innerHTML=t;var a=this;$(this.footer).append(buttonElm),null!==i&&$(buttonElm).click(function(t){i.call(a,this,t)})},this.doRemote=function(t,a,n){var s=this;$.ajax({url:t,method:a,data:n,async:!1,beforeSend:function(){o.call(s)},error:function(t){e.call(s,t)},success:function(t){i.call(s,t)},contentType:!1,cache:!1,processData:!1})},this.setupFormSubmit=function(t,o){if(void 0===o)console.warn("Modal has form but does not have a submit button");else{var e=this;$(o).click(function(o){var i;i=window.FormData?new FormData($(t)[0]):$(t).serializeArray(),e.doRemote($(t).attr("action"),$(t).hasAttr("method")?$(t).attr("method"):"GET",i)})}},this.confirmModal=function(t,o,e,i,a,n,s,d){this.show(),this.setSize(a),void 0!==t&&this.setTitle(t),this.setContent(''+o);var l=this;this.addFooterButton(void 0===e?this.defaults.okLabel:e,"submit","btn btn-primary",function(t){var o;window.FormData?(o=new FormData($("#ModalRemoteConfirmForm")[0]),"undefined"!=typeof d&&d&&o.append("pks",d.join())):(o=$("#ModalRemoteConfirmForm"),"undefined"!=typeof d&&d&&(o.pks=d),o=o.serializeArray()),l.doRemote(n,s,o)}),this.addFooterButton(void 0===i?this.defaults.cancelLabel:i,"button","btn btn-default pull-left",function(t){this.hide()})},this.open=function(t,o){$(t).hasAttr("data-confirm-title")||$(t).hasAttr("data-confirm-message")?this.confirmModal($(t).attr("data-confirm-title"),$(t).attr("data-confirm-message"),$(t).attr("data-confirm-ok"),$(t).attr("data-confirm-cancel"),$(t).hasAttr("data-modal-size")?$(t).attr("data-modal-size"):"normal",$(t).hasAttr("href")?$(t).attr("href"):$(t).attr("data-url"),$(t).hasAttr("data-request-method")?$(t).attr("data-request-method"):"GET",o):this.doRemote($(t).hasAttr("href")?$(t).attr("href"):$(t).attr("data-url"),$(t).hasAttr("data-request-method")?$(t).attr("data-request-method"):"GET",o)}}!function(t){t.fn.hasAttr=function(t){return void 0!==this.attr(t)}}(jQuery); diff --git a/src/assets/ajaxcrud.css b/src/assets/ajaxcrud.css new file mode 100644 index 0000000..3cf38ad --- /dev/null +++ b/src/assets/ajaxcrud.css @@ -0,0 +1,8 @@ +.kv-grid-toolbar button.ui-corner-all{ + border-radius: 0 4px 4px 0 !important; + height: 34px; +} + +.kv-grid-toolbar button.ui-corner-all > span.ui-button-text{ + background: #fff; +} \ No newline at end of file diff --git a/src/assets/ajaxcrud.js b/src/assets/ajaxcrud.js new file mode 100644 index 0000000..af2a171 --- /dev/null +++ b/src/assets/ajaxcrud.js @@ -0,0 +1,58 @@ +/*! + * Ajax Crud + * ================================= + * Use for johnitvn/yii2-ajaxcrud extension + * @author John Martin john.itvn@gmail.com + */ +$(document).ready(function () { + + // Create instance of Modal Remote + // This instance will be the controller of all business logic of modal + // Backwards compatible lookup of old ajaxCrubModal ID + if ($('#ajaxCrubModal').length > 0 && $('#ajaxCrudModal').length == 0) { + modal = new ModalRemote('#ajaxCrubModal'); + } else { + modal = new ModalRemote('#ajaxCrudModal'); + } + + // Catch click event on all buttons that want to open a modal + $(document).on('click', '[role="modal-remote"]', function (event) { + event.preventDefault(); + + // Open modal + modal.open(this, null); + }); + + // Catch click event on all buttons that want to open a modal + // with bulk action + $(document).on('click', '[role="modal-remote-bulk"]', function (event) { + event.preventDefault(); + + // Collect all selected ID's + var selectedIds = []; + + // See if we have a selector set + var selection = 'selection'; + if ($(this).data("selector") != null) { + selection = $(this).data("selector"); + } + + $('input:checkbox[name="' + selection + '[]"]').each(function () { + if (this.checked) + selectedIds.push($(this).val()); + }); + + if (selectedIds.length == 0) { + // If no selected ID's show warning + modal.show(); + modal.setTitle('No selection'); + modal.setContent('You must select item(s) to use this action'); + modal.addFooterButton("Close", 'btn btn-default', function (button, event) { + this.hide(); + }); + } else { + // Open modal + modal.open(this, selectedIds); + } + }); +}); diff --git a/src/assets/ajaxcrud.min.js b/src/assets/ajaxcrud.min.js new file mode 100644 index 0000000..7c43c69 --- /dev/null +++ b/src/assets/ajaxcrud.min.js @@ -0,0 +1 @@ +$(document).ready(function(){$("#ajaxCrubModal").length>0&&0==$("#ajaxCrudModal").length?modal=new ModalRemote("#ajaxCrubModal"):modal=new ModalRemote("#ajaxCrudModal"),$(document).on("click",'[role="modal-remote"]',function(e){e.preventDefault(),modal.open(this,null)}),$(document).on("click",'[role="modal-remote-bulk"]',function(e){e.preventDefault();var o=[];$('input:checkbox[name="selection[]"]').each(function(){this.checked&&o.push($(this).val())}),0==o.length?(modal.show(),modal.setTitle("No selection"),modal.setContent("You must select item(s) to use this action"),modal.addFooterButton("Close","btn btn-default",function(e,o){this.hide()})):modal.open(this,o)})}); \ No newline at end of file diff --git a/src/generators/Generator.php b/src/generators/Generator.php new file mode 100644 index 0000000..7e924e2 --- /dev/null +++ b/src/generators/Generator.php @@ -0,0 +1,529 @@ + + * @since 1.0 + */ +class Generator extends \yii\gii\Generator +{ + public $modelClass; + public $controllerClass; + public $viewPath; + public $baseControllerClass = 'yii\web\Controller'; + public $searchModelClass = ''; + + /** + * @inheritdoc + */ + public function getName() + { + return 'Ajax CRUD Generator'; + } + + /** + * @inheritdoc + */ + public function getDescription() + { + return 'This generator generates a controller and views that implement CRUD (Create, Read, Update, Delete) + operations for the specified data model with template for Single Page Ajax Administration'; + } + + /** + * @inheritdoc + */ + public function rules() + { + return array_merge(parent::rules(), [ + [['controllerClass', 'modelClass', 'searchModelClass', 'baseControllerClass'], 'filter', 'filter' => 'trim'], + [['modelClass', 'controllerClass', 'baseControllerClass'], 'required'], + [['searchModelClass'], 'compare', 'compareAttribute' => 'modelClass', 'operator' => '!==', 'message' => 'Search Model Class must not be equal to Model Class.'], + [['modelClass', 'controllerClass', 'baseControllerClass', 'searchModelClass'], 'match', 'pattern' => '/^[\w\\\\]*$/', 'message' => 'Only word characters and backslashes are allowed.'], + [['modelClass'], 'validateClass', 'params' => ['extends' => BaseActiveRecord::className()]], + [['baseControllerClass'], 'validateClass', 'params' => ['extends' => Controller::className()]], + [['controllerClass'], 'match', 'pattern' => '/Controller$/', 'message' => 'Controller class name must be suffixed with "Controller".'], + [['controllerClass'], 'match', 'pattern' => '/(^|\\\\)[A-Z][^\\\\]+Controller$/', 'message' => 'Controller class name must start with an uppercase letter.'], + [['controllerClass', 'searchModelClass'], 'validateNewClass'], + [['modelClass'], 'validateModelClass'], + [['enableI18N'], 'boolean'], + [['messageCategory'], 'validateMessageCategory', 'skipOnEmpty' => false], + ['viewPath', 'safe'], + ]); + } + + /** + * @inheritdoc + */ + public function attributeLabels() + { + return array_merge(parent::attributeLabels(), [ + 'modelClass' => 'Model Class', + 'controllerClass' => 'Controller Class', + 'viewPath' => 'View Path', + 'baseControllerClass' => 'Base Controller Class', + 'searchModelClass' => 'Search Model Class', + ]); + } + + + /** + * @inheritdoc + */ + public function hints() + { + return array_merge(parent::hints(), [ + 'modelClass' => 'This is the ActiveRecord class associated with the table that CRUD will be built upon. + You should provide a fully qualified class name, e.g., app\models\Post.', + 'controllerClass' => 'This is the name of the controller class to be generated. You should + provide a fully qualified namespaced class (e.g. app\controllers\PostController), + and class name should be in CamelCase with an uppercase first letter. Make sure the class + is using the same namespace as specified by your application\'s controllerNamespace property.', + 'viewPath' => 'Specify the directory for storing the view scripts for the controller. You may use path alias here, e.g., + /var/www/basic/controllers/views/post, @app/views/post. If not set, it will default + to @app/views/ControllerID', + 'baseControllerClass' => 'This is the class that the new CRUD controller class will extend from. + You should provide a fully qualified class name, e.g., yii\web\Controller.', + 'searchModelClass' => 'This is the name of the search model class to be generated. You should provide a fully + qualified namespaced class name, e.g., app\models\PostSearch.', + ]); + } + + /** + * @inheritdoc + */ + public function requiredTemplates() + { + return ['controller.php']; + } + + /** + * @inheritdoc + */ + public function stickyAttributes() + { + return array_merge(parent::stickyAttributes(), ['baseControllerClass']); + } + + /** + * Checks if model class is valid + */ + public function validateModelClass() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + if (empty($pk)) { + $this->addError('modelClass', "The table associated with $class must have primary key(s)."); + } + } + + /** + * @inheritdoc + */ + public function generate() + { + $controllerFile = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->controllerClass, '\\')) . '.php'); + $files = [ + new CodeFile($controllerFile, $this->render('controller.php')), + ]; + + if (!empty($this->searchModelClass)) { + $searchModel = Yii::getAlias('@' . str_replace('\\', '/', ltrim($this->searchModelClass, '\\') . '.php')); + $files[] = new CodeFile($searchModel, $this->render('search.php')); + } + + $viewPath = $this->getViewPath(); + $templatePath = $this->getTemplatePath() . '/views'; + foreach (scandir($templatePath) as $file) { + if (empty($this->searchModelClass) && $file === '_search.php') { + continue; + } + if (is_file($templatePath . '/' . $file) && pathinfo($file, PATHINFO_EXTENSION) === 'php') { + $files[] = new CodeFile("$viewPath/$file", $this->render("views/$file")); + } + } + + return $files; + } + + /** + * @return string the controller ID (without the module ID prefix) + */ + public function getControllerID() + { + $pos = strrpos($this->controllerClass, '\\'); + $class = substr(substr($this->controllerClass, $pos + 1), 0, -10); + + return Inflector::camel2id($class); + } + + /** + * @return string the controller view path + */ + public function getViewPath() + { + if (empty($this->viewPath)) { + return Yii::getAlias('@app/views/' . $this->getControllerID()); + } else { + return Yii::getAlias($this->viewPath); + } + } + + public function getNameAttribute() + { + foreach ($this->getColumnNames() as $name) { + if (!strcasecmp($name, 'name') || !strcasecmp($name, 'title')) { + return $name; + } + } + /* @var $class \yii\db\ActiveRecord */ + $class = $this->modelClass; + $pk = $class::primaryKey(); + + return $pk[0]; + } + + /** + * Generates code for active field + * @param string $attribute + * @return string + */ + public function generateActiveField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false || !isset($tableSchema->columns[$attribute])) { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $attribute)) { + return "\$form->field(\$model, '$attribute')->passwordInput()"; + } else { + return "\$form->field(\$model, '$attribute')"; + } + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox()"; + } elseif ($column->type === 'text') { + return "\$form->field(\$model, '$attribute')->textarea(['rows' => 6])"; + } else { + if (preg_match('/^(password|pass|passwd|passcode)$/i', $column->name)) { + $input = 'passwordInput'; + } else { + $input = 'textInput'; + } + if (is_array($column->enumValues) && count($column->enumValues) > 0) { + $dropDownOptions = []; + foreach ($column->enumValues as $enumValue) { + $dropDownOptions[$enumValue] = Inflector::humanize($enumValue); + } + return "\$form->field(\$model, '$attribute')->dropDownList(" + . preg_replace("/\n\s*/", ' ', VarDumper::export($dropDownOptions)).", ['prompt' => ''])"; + } elseif ($column->phpType !== 'string' || $column->size === null) { + return "\$form->field(\$model, '$attribute')->$input()"; + } else { + return "\$form->field(\$model, '$attribute')->$input(['maxlength' => true])"; + } + } + } + + /** + * Generates code for active search field + * @param string $attribute + * @return string + */ + public function generateActiveSearchField($attribute) + { + $tableSchema = $this->getTableSchema(); + if ($tableSchema === false) { + return "\$form->field(\$model, '$attribute')"; + } + $column = $tableSchema->columns[$attribute]; + if ($column->phpType === 'boolean') { + return "\$form->field(\$model, '$attribute')->checkbox()"; + } else { + return "\$form->field(\$model, '$attribute')"; + } + } + + /** + * Generates column format + * @param \yii\db\ColumnSchema $column + * @return string + */ + public function generateColumnFormat($column) + { + if ($column->phpType === 'boolean') { + return 'boolean'; + } elseif ($column->type === 'text') { + return 'ntext'; + } elseif (stripos($column->name, 'time') !== false && $column->phpType === 'integer') { + return 'datetime'; + } elseif (stripos($column->name, 'email') !== false) { + return 'email'; + } elseif (stripos($column->name, 'url') !== false) { + return 'url'; + } else { + return 'text'; + } + } + + /** + * Generates validation rules for the search model. + * @return array the generated validation rules + */ + public function generateSearchRules() + { + if (($table = $this->getTableSchema()) === false) { + return ["[['" . implode("', '", $this->getColumnNames()) . "'], 'safe']"]; + } + $types = []; + foreach ($table->columns as $column) { + switch ($column->type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + $types['integer'][] = $column->name; + break; + case Schema::TYPE_BOOLEAN: + $types['boolean'][] = $column->name; + break; + case Schema::TYPE_FLOAT: + case Schema::TYPE_DOUBLE: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + $types['number'][] = $column->name; + break; + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + default: + $types['safe'][] = $column->name; + break; + } + } + + $rules = []; + foreach ($types as $type => $columns) { + $rules[] = "[['" . implode("', '", $columns) . "'], '$type']"; + } + + return $rules; + } + + /** + * @return array searchable attributes + */ + public function getSearchAttributes() + { + return $this->getColumnNames(); + } + + /** + * Generates the attribute labels for the search model. + * @return array the generated attribute labels (name => label) + */ + public function generateSearchLabels() + { + /* @var $model \yii\base\Model */ + $model = new $this->modelClass(); + $attributeLabels = $model->attributeLabels(); + $labels = []; + foreach ($this->getColumnNames() as $name) { + if (isset($attributeLabels[$name])) { + $labels[$name] = $attributeLabels[$name]; + } else { + if (!strcasecmp($name, 'id')) { + $labels[$name] = 'ID'; + } else { + $label = Inflector::camel2words($name); + if (!empty($label) && substr_compare($label, ' id', -3, 3, true) === 0) { + $label = substr($label, 0, -3) . ' ID'; + } + $labels[$name] = $label; + } + } + } + + return $labels; + } + + /** + * Generates search conditions + * @return array + */ + public function generateSearchConditions() + { + $columns = []; + if (($table = $this->getTableSchema()) === false) { + $class = $this->modelClass; + /* @var $model \yii\base\Model */ + $model = new $class(); + foreach ($model->attributes() as $attribute) { + $columns[$attribute] = 'unknown'; + } + } else { + foreach ($table->columns as $column) { + $columns[$column->name] = $column->type; + } + } + + $likeConditions = []; + $hashConditions = []; + foreach ($columns as $column => $type) { + switch ($type) { + case Schema::TYPE_SMALLINT: + case Schema::TYPE_INTEGER: + case Schema::TYPE_BIGINT: + case Schema::TYPE_BOOLEAN: + case Schema::TYPE_FLOAT: + case Schema::TYPE_DOUBLE: + case Schema::TYPE_DECIMAL: + case Schema::TYPE_MONEY: + case Schema::TYPE_DATE: + case Schema::TYPE_TIME: + case Schema::TYPE_DATETIME: + case Schema::TYPE_TIMESTAMP: + $hashConditions[] = "'{$column}' => \$this->{$column},"; + break; + default: + $likeConditions[] = "->andFilterWhere(['like', '{$column}', \$this->{$column}])"; + break; + } + } + + $conditions = []; + if (!empty($hashConditions)) { + $conditions[] = "\$query->andFilterWhere([\n" + . str_repeat(' ', 12) . implode("\n" . str_repeat(' ', 12), $hashConditions) + . "\n" . str_repeat(' ', 8) . "]);\n"; + } + if (!empty($likeConditions)) { + $conditions[] = "\$query" . implode("\n" . str_repeat(' ', 12), $likeConditions) . ";\n"; + } + + return $conditions; + } + + /** + * Generates URL parameters + * @return string + */ + public function generateUrlParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + return "'id' => (string)\$model->{$pks[0]}"; + } else { + return "'id' => \$model->{$pks[0]}"; + } + } else { + $params = []; + foreach ($pks as $pk) { + if (is_subclass_of($class, 'yii\mongodb\ActiveRecord')) { + $params[] = "'$pk' => (string)\$model->$pk"; + } else { + $params[] = "'$pk' => \$model->$pk"; + } + } + + return implode(', ', $params); + } + } + + /** + * Generates action parameters + * @return string + */ + public function generateActionParams() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (count($pks) === 1) { + return '$id'; + } else { + return '$' . implode(', $', $pks); + } + } + + /** + * Generates parameter tags for phpdoc + * @return array parameter tags for phpdoc + */ + public function generateActionParamComments() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + $pks = $class::primaryKey(); + if (($table = $this->getTableSchema()) === false) { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . (substr(strtolower($pk), -2) == 'id' ? 'integer' : 'string') . ' $' . $pk; + } + + return $params; + } + if (count($pks) === 1) { + return ['@param ' . $table->columns[$pks[0]]->phpType . ' $id']; + } else { + $params = []; + foreach ($pks as $pk) { + $params[] = '@param ' . $table->columns[$pk]->phpType . ' $' . $pk; + } + + return $params; + } + } + + /** + * Returns table schema for current model class or false if it is not an active record + * @return boolean|\yii\db\TableSchema + */ + public function getTableSchema() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema(); + } else { + return false; + } + } + + /** + * @return array model column names + */ + public function getColumnNames() + { + /* @var $class ActiveRecord */ + $class = $this->modelClass; + if (is_subclass_of($class, 'yii\db\ActiveRecord')) { + return $class::getTableSchema()->getColumnNames(); + } else { + /* @var $model \yii\base\Model */ + $model = new $class(); + + return $model->attributes(); + } + } +} diff --git a/src/generators/default/controller.php b/src/generators/default/controller.php new file mode 100644 index 0000000..96a7120 --- /dev/null +++ b/src/generators/default/controller.php @@ -0,0 +1,323 @@ +controllerClass); +$modelClass = StringHelper::basename($generator->modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $searchModelAlias = $searchModelClass . 'Search'; +} + +/* @var $class ActiveRecordInterface */ +$class = $generator->modelClass; +$pks = $class::primaryKey(); +$urlParams = $generator->generateUrlParams(); +$actionParams = $generator->generateActionParams(); +$actionParamComments = $generator->generateActionParamComments(); + +echo " + +namespace controllerClass, '\\')) ?>; + +use Yii; +use modelClass, '\\') ?>; +searchModelClass)): ?> +use searchModelClass, '\\') . (isset($searchModelAlias) ? " as $searchModelAlias" : "") ?>; + +use yii\data\ActiveDataProvider; + +use baseControllerClass, '\\') ?>; +use yii\web\NotFoundHttpException; +use yii\filters\VerbFilter; +use \yii\web\Response; +use yii\helpers\Html; + +/** + * implements the CRUD actions for model. + */ +class extends baseControllerClass) . "\n" ?> +{ + /** + * @inheritdoc + */ + public function behaviors() + { + return [ + 'verbs' => [ + 'class' => VerbFilter::className(), + 'actions' => [ + 'delete' => ['post'], + 'bulkdelete' => ['post'], + ], + ], + ]; + } + + /** + * Lists all models. + * @return mixed + */ + public function actionIndex() + { + searchModelClass)): ?> + $searchModel = new (); + $dataProvider = $searchModel->search(Yii::$app->request->queryParams); + + return $this->render('index', [ + 'searchModel' => $searchModel, + 'dataProvider' => $dataProvider, + ]); + + $dataProvider = new ActiveDataProvider([ + 'query' => ::find(), + ]); + + return $this->render('index', [ + 'dataProvider' => $dataProvider, + ]); + + } + + + /** + * Displays a single model. + * + * @return mixed + */ + public function actionView() + { + $request = Yii::$app->request; + if($request->isAjax){ + Yii::$app->response->format = Response::FORMAT_JSON; + return [ + 'title'=> " #"., + 'content'=>$this->renderAjax('view', [ + 'model' => $this->findModel(), + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::a('Edit',['update',''=>],['class'=>'btn btn-primary','role'=>'modal-remote']) + ]; + }else{ + return $this->render('view', [ + 'model' => $this->findModel(), + ]); + } + } + + /** + * Creates a new model. + * For ajax request will return json object + * and for non-ajax request if creation is successful, the browser will be redirected to the 'view' page. + * @return mixed + */ + public function actionCreate() + { + $request = Yii::$app->request; + $model = new (); + + if($request->isAjax){ + /* + * Process for ajax request + */ + Yii::$app->response->format = Response::FORMAT_JSON; + if($request->isGet){ + return [ + 'title'=> "Create new ", + 'content'=>$this->renderAjax('create', [ + 'model' => $model, + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::button('Save',['class'=>'btn btn-primary','type'=>"submit"]) + + ]; + }else if($model->load($request->post()) && $model->save()){ + return [ + 'forceReload'=>'#crud-datatable-pjax', + 'title'=> "Create new ", + 'content'=>'Create success', + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::a('Create More',['create'],['class'=>'btn btn-primary','role'=>'modal-remote']) + + ]; + }else{ + return [ + 'title'=> "Create new ", + 'content'=>$this->renderAjax('create', [ + 'model' => $model, + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::button('Save',['class'=>'btn btn-primary','type'=>"submit"]) + + ]; + } + }else{ + /* + * Process for non-ajax request + */ + if ($model->load($request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('create', [ + 'model' => $model, + ]); + } + } + + } + + /** + * Updates an existing model. + * For ajax request will return json object + * and for non-ajax request if update is successful, the browser will be redirected to the 'view' page. + * + * @return mixed + */ + public function actionUpdate() + { + $request = Yii::$app->request; + $model = $this->findModel(); + + if($request->isAjax){ + /* + * Process for ajax request + */ + Yii::$app->response->format = Response::FORMAT_JSON; + if($request->isGet){ + return [ + 'title'=> "Update #"., + 'content'=>$this->renderAjax('update', [ + 'model' => $model, + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::button('Save',['class'=>'btn btn-primary','type'=>"submit"]) + ]; + }else if($model->load($request->post()) && $model->save()){ + return [ + 'forceReload'=>'#crud-datatable-pjax', + 'title'=> " #"., + 'content'=>$this->renderAjax('view', [ + 'model' => $model, + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::a('Edit',['update',''=>],['class'=>'btn btn-primary','role'=>'modal-remote']) + ]; + }else{ + return [ + 'title'=> "Update #"., + 'content'=>$this->renderAjax('update', [ + 'model' => $model, + ]), + 'footer'=> Html::button('Close',['class'=>'btn btn-default pull-left','data-dismiss'=>"modal"]). + Html::button('Save',['class'=>'btn btn-primary','type'=>"submit"]) + ]; + } + }else{ + /* + * Process for non-ajax request + */ + if ($model->load($request->post()) && $model->save()) { + return $this->redirect(['view', ]); + } else { + return $this->render('update', [ + 'model' => $model, + ]); + } + } + } + + /** + * Delete an existing model. + * For ajax request will return json object + * and for non-ajax request if deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed + */ + public function actionDelete() + { + $request = Yii::$app->request; + $this->findModel()->delete(); + + if($request->isAjax){ + /* + * Process for ajax request + */ + Yii::$app->response->format = Response::FORMAT_JSON; + return ['forceClose'=>true,'forceReload'=>'#crud-datatable-pjax']; + }else{ + /* + * Process for non-ajax request + */ + return $this->redirect(['index']); + } + + + } + + /** + * Delete multiple existing model. + * For ajax request will return json object + * and for non-ajax request if deletion is successful, the browser will be redirected to the 'index' page. + * + * @return mixed + */ + public function actionBulkdelete() + { + $request = Yii::$app->request; + $pks = explode(',', $request->post( 'pks' )); // Array or selected records primary keys + foreach ( $pks as $pk ) { + $model = $this->findModel($pk); + $model->delete(); + } + + if($request->isAjax){ + /* + * Process for ajax request + */ + Yii::$app->response->format = Response::FORMAT_JSON; + return ['forceClose'=>true,'forceReload'=>'#crud-datatable-pjax']; + }else{ + /* + * Process for non-ajax request + */ + return $this->redirect(['index']); + } + + } + + /** + * Finds the model based on its primary key value. + * If the model is not found, a 404 HTTP exception will be thrown. + * + * @return the loaded model + * @throws NotFoundHttpException if the model cannot be found + */ + protected function findModel() + { + \$$pk"; + } + $condition = '[' . implode(', ', $condition) . ']'; +} +?> + if (($model = ::findOne()) !== null) { + return $model; + } else { + throw new NotFoundHttpException('The requested page does not exist.'); + } + } +} diff --git a/src/generators/default/search.php b/src/generators/default/search.php new file mode 100644 index 0000000..b868352 --- /dev/null +++ b/src/generators/default/search.php @@ -0,0 +1,84 @@ +modelClass); +$searchModelClass = StringHelper::basename($generator->searchModelClass); +if ($modelClass === $searchModelClass) { + $modelAlias = $modelClass . 'Model'; +} +$rules = $generator->generateSearchRules(); +$labels = $generator->generateSearchLabels(); +$searchAttributes = $generator->getSearchAttributes(); +$searchConditions = $generator->generateSearchConditions(); + +echo " + +namespace searchModelClass, '\\')) ?>; + +use Yii; +use yii\base\Model; +use yii\data\ActiveDataProvider; +use modelClass, '\\') . (isset($modelAlias) ? " as $modelAlias" : "") ?>; + +/** + * represents the model behind the search form about `modelClass ?>`. + */ +class extends + +{ + /** + * @inheritdoc + */ + public function rules() + { + return [ + , + ]; + } + + /** + * @inheritdoc + */ + public function scenarios() + { + // bypass scenarios() implementation in the parent class + return Model::scenarios(); + } + + /** + * Creates data provider instance with search query applied + * + * @param array $params + * + * @return ActiveDataProvider + */ + public function search($params) + { + $query = ::find(); + + $dataProvider = new ActiveDataProvider([ + 'query' => $query, + ]); + + $this->load($params); + + if (!$this->validate()) { + // uncomment the following line if you do not want to return any records when validation fails + // $query->where('0=1'); + return $dataProvider; + } + + + + return $dataProvider; + } +} diff --git a/src/generators/default/views/_columns.php b/src/generators/default/views/_columns.php new file mode 100644 index 0000000..9ddc45f --- /dev/null +++ b/src/generators/default/views/_columns.php @@ -0,0 +1,70 @@ +modelClass); +$urlParams = $generator->generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); +$actionParams = $generator->generateActionParams(); + +echo " +use yii\helpers\Url; + +return [ + [ + 'class' => 'kartik\grid\CheckboxColumn', + 'width' => '20px', + ], + [ + 'class' => 'kartik\grid\SerialColumn', + 'width' => '30px', + ], + getColumnNames() as $name) { + if ($name=='id'||$name=='created_at'||$name=='updated_at'){ + echo " // [\n"; + echo " // 'class'=>'\kartik\grid\DataColumn',\n"; + echo " // 'attribute'=>'" . $name . "',\n"; + echo " // ],\n"; + } else if (++$count < 6) { + echo " [\n"; + echo " 'class'=>'\kartik\grid\DataColumn',\n"; + echo " 'attribute'=>'" . $name . "',\n"; + echo " ],\n"; + } else { + echo " // [\n"; + echo " // 'class'=>'\kartik\grid\DataColumn',\n"; + echo " // 'attribute'=>'" . $name . "',\n"; + echo " // ],\n"; + } + } + ?> + [ + 'class' => 'kartik\grid\ActionColumn', + 'dropdown' => false, + 'vAlign'=>'middle', + 'urlCreator' => function($action, $model, $key, $index) { + return Url::to([$action,''=>$key]); + }, + 'viewOptions'=>['role'=>'modal-remote','title'=>'View','data-toggle'=>'tooltip'], + 'updateOptions'=>['role'=>'modal-remote','title'=>'Update', 'data-toggle'=>'tooltip'], + 'deleteOptions'=>['role'=>'modal-remote','title'=>'Delete', + 'data-confirm'=>false, 'data-method'=>false,// for overide yii data api + 'data-request-method'=>'post', + 'data-toggle'=>'tooltip', + 'data-confirm-title'=>'Are you sure?', + 'data-confirm-message'=>'Are you sure want to delete this item'], + ], + +]; \ No newline at end of file diff --git a/src/generators/default/views/_form.php b/src/generators/default/views/_form.php new file mode 100644 index 0000000..7b5a659 --- /dev/null +++ b/src/generators/default/views/_form.php @@ -0,0 +1,43 @@ +modelClass(); +$safeAttributes = $model->safeAttributes(); +if (empty($safeAttributes)) { + $safeAttributes = $model->attributes(); +} + +echo " +use yii\helpers\Html; +use yii\widgets\ActiveForm; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ +/* @var $form yii\widgets\ActiveForm */ +?> + +
+ + $form = ActiveForm::begin(); ?> + +getColumnNames() as $attribute) { + if (in_array($attribute, $safeAttributes)) { + echo " generateActiveField($attribute) . " ?>\n\n"; + } +} ?> + request->isAjax){ ?>'."\n"?> +
+ Html::submitButton($model->isNewRecord ? generateString('Create') ?> : generateString('Update') ?>, ['class' => $model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']) ?> +
+ \n"?> + + ActiveForm::end(); ?> + +
diff --git a/src/generators/default/views/create.php b/src/generators/default/views/create.php new file mode 100644 index 0000000..9832979 --- /dev/null +++ b/src/generators/default/views/create.php @@ -0,0 +1,23 @@ + + +use yii\helpers\Html; + + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ + +?> +
+ $this->render('_form', [ + 'model' => $model, + ]) ?> +
diff --git a/src/generators/default/views/index.php b/src/generators/default/views/index.php new file mode 100644 index 0000000..10d93ee --- /dev/null +++ b/src/generators/default/views/index.php @@ -0,0 +1,81 @@ +generateUrlParams(); +$nameAttribute = $generator->getNameAttribute(); +echo " +use yii\helpers\Url; +use yii\helpers\Html; +use yii\bootstrap\Modal; +use kartik\grid\GridView; +use johnitvn\ajaxcrud\CrudAsset; +use johnitvn\ajaxcrud\BulkButtonWidget; + +/* @var $this yii\web\View */ +searchModelClass) ? "/* @var \$searchModel " . ltrim($generator->searchModelClass, '\\') . " */\n" : '' ?> +/* @var $dataProvider yii\data\ActiveDataProvider */ + +$this->title = generateString(Inflector::pluralize(Inflector::camel2words(StringHelper::basename($generator->modelClass)))) ?>; +$this->params['breadcrumbs'][] = $this->title; + +CrudAsset::register($this); + +?> +
+
+ GridView::widget([ + 'id'=>'crud-datatable', + 'dataProvider' => $dataProvider, + 'filterModel' => $searchModel, + 'pjax'=>true, + 'columns' => require(__DIR__.'/_columns.php'), + 'toolbar'=> [ + ['content'=> + Html::a('', ['create'], + ['role'=>'modal-remote','title'=> 'Create new modelClass))) ?>','class'=>'btn btn-default']). + Html::a('', [''], + ['data-pjax'=>1, 'class'=>'btn btn-default', 'title'=>'Reset Grid']). + '{toggleData}'. + '{export}' + ], + ], + 'striped' => true, + 'condensed' => true, + 'responsive' => true, + 'panel' => [ + 'type' => 'primary', + 'heading' => ' modelClass))) ?> listing', + 'before'=>'* Resize table columns just like a spreadsheet by dragging the column edges.', + 'after'=>BulkButtonWidget::widget([ + 'buttons'=>Html::a('  Delete All', + ["bulkdelete"] , + [ + "class"=>"btn btn-danger btn-xs", + 'role'=>'modal-remote-bulk', + 'data-confirm'=>false, 'data-method'=>false,// for overide yii data api + 'data-request-method'=>'post', + 'data-confirm-title'=>'Are you sure?', + 'data-confirm-message'=>'Are you sure want to delete this item' + ]), + ]). + '
', + ] + ])\n"?> +
+
+"ajaxCrudModal", + "footer"=>"",// always need it for jquery plugin +])?>'."\n"?> +'?> + diff --git a/src/generators/default/views/update.php b/src/generators/default/views/update.php new file mode 100644 index 0000000..f3221e7 --- /dev/null +++ b/src/generators/default/views/update.php @@ -0,0 +1,23 @@ + + +use yii\helpers\Html; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ +?> +
+ + $this->render('_form', [ + 'model' => $model, + ]) ?> + +
diff --git a/src/generators/default/views/view.php b/src/generators/default/views/view.php new file mode 100644 index 0000000..17bcf6c --- /dev/null +++ b/src/generators/default/views/view.php @@ -0,0 +1,39 @@ +generateUrlParams(); + +echo " + +use yii\widgets\DetailView; + +/* @var $this yii\web\View */ +/* @var $model modelClass, '\\') ?> */ +?> +
+ + DetailView::widget([ + 'model' => $model, + 'attributes' => [ +getTableSchema()) === false) { + foreach ($generator->getColumnNames() as $name) { + echo " '" . $name . "',\n"; + } + } else { + foreach ($generator->getTableSchema()->columns as $column) { + $format = $generator->generateColumnFormat($column); + echo " '" . $column->name . ($format === 'text' ? "" : ":" . $format) . "',\n"; + } + } + ?> + ], + ]) ?> + +
diff --git a/src/generators/form.php b/src/generators/form.php new file mode 100644 index 0000000..5050f5b --- /dev/null +++ b/src/generators/form.php @@ -0,0 +1,13 @@ +General Configuration'; +echo $form->field($generator, 'modelClass'); +echo $form->field($generator, 'searchModelClass'); +echo $form->field($generator, 'controllerClass'); +echo $form->field($generator, 'viewPath'); +echo $form->field($generator, 'baseControllerClass'); +echo $form->field($generator, 'enableI18N')->checkbox(); +echo $form->field($generator, 'messageCategory');