diff --git a/src/BulkButtonWidget.php b/src/BulkButtonWidget.php index cd2ed5e..ae44be8 100644 --- a/src/BulkButtonWidget.php +++ b/src/BulkButtonWidget.php @@ -1,24 +1,196 @@ 'bulk-actions' ]; + + /** + * @var string the ID of the controller that should handle the actions specified here. + * If not set, it will use the currently active controller. This property is mainly used by + * [[urlCreator]] to create URLs for different actions. The value of this property will be prefixed + * to each action name to form the route of the action. + */ + public $controller; + + /** + * @var string the template used for composing each cell in the action column. + * Tokens enclosed within curly brackets are treated as controller action IDs (also called *button names* + * in the context of action column). They will be replaced by the corresponding button rendering callbacks + * specified in [[buttons]]. For example, the token `{view}` will be replaced by the result of + * the callback `buttons['view']`. If a callback cannot be found, the token will be replaced with an empty string. + * + * @see buttons + */ + public $template = '{bulk-delete}'; + + /** + * @var array button rendering callbacks. The array keys are the button names (without curly brackets), + * and the values are the corresponding button rendering callbacks. The callbacks should use the following + * signature: + * + * ```php + * function ($url) { + * // return the button HTML code + * } + * ``` + * + * where `$url` is the URL that the column creates for the button. + * + * ```php + * [ + * 'update' => function ($url) { + * return Html::a('Update', $url); + * }, + * ], + * ``` + */ + public $buttons = []; -class BulkButtonWidget extends Widget{ + /** @var array visibility conditions for each button. The array keys are the button names (without curly brackets), + * and the values are the boolean true/false or the anonymous function. When the button name is not specified in + * this array it will be shown by default. + * The callbacks must use the following signature: + * + * Pass a boolean value: + * + * ```php + * [ + * 'update' => \Yii::$app->user->can('update'), + * ], + * ``` + * @since 2.0.7 + */ - public $buttons; - - public function init(){ + public $visibleButtons = []; + + /** + * @var callable a callback that creates a button URL using the specified model information. + * The signature of the callback should be the same as that of [[createUrl()]] + * Since 2.0.10 it can accept additional parameter, which refers to the column instance itself: + * + * ```php + * function (string $action) { + * //return string; + * } + * ``` + * + * If this property is not set, button URLs will be created using [[createUrl()]]. + */ + public $urlCreator; + + /** + * @var array html options to be applied to the [[initDefaultButton()|default button]]. + * @since 2.0.4 + */ + public $buttonOptions = []; + + + public function init() { parent::init(); - + $this->initDefaultButtons(); + } + + /** + * Initializes the default button rendering callbacks. + */ + protected function initDefaultButtons() { + $this->initDefaultButton( 'bulk-delete', 'trash', [ + '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' => yii::t( 'app', 'Are you sure?' ), + 'data-confirm-message' => yii::t( 'app', 'Are you sure want to delete these items' ), + ] ); } - - public function run(){ - $content = '
'. - '  With selected  '. - $this->buttons. - '
'; - return $content; + + /** + * Initializes the default button rendering callback for single button + * + * @param string $name Button name as it's written in template + * @param string $iconName The part of Bootstrap glyphicon class that makes it unique + * @param array $additionalOptions Array of additional options + * + * @since 2.0.11 + */ + protected function initDefaultButton( $name, $iconName, $additionalOptions = [] ) { + if ( ! isset( $this->buttons[ $name ] ) && strpos( $this->template, '{' . $name . '}' ) !== false ) { + $this->buttons[ $name ] = function ( $url ) use ( $name, $iconName, $additionalOptions ) { + switch ( $name ) { + case 'bulk-delete': + $title = Yii::t( 'yii', 'Delete' ); + break; + default: + $title = ucfirst( $name ); + } + $options = array_merge( [ + 'title' => $title, + 'aria-label' => $title, + ], $additionalOptions, $this->buttonOptions ); + $icon = Html::tag( 'span', '', [ 'class' => "glyphicon glyphicon-$iconName" ] ); + + return Html::a( $icon . ' ' . $title, $url, $options ); + }; + } + } + + public function run() { + $content = preg_replace_callback( '/\\{([\w\-\/]+)\\}/', function ( $matches ) { + $name = $matches[1]; + + if ( isset( $this->visibleButtons[ $name ] ) ) { + $isVisible = $this->visibleButtons[ $name ] instanceof \Closure + ? call_user_func( $this->visibleButtons[ $name ] ) + : $this->visibleButtons[ $name ]; + } else { + $isVisible = true; + } + + if ( $isVisible && isset( $this->buttons[ $name ] ) ) { + $url = $this->createUrl( $name ); + + return call_user_func( $this->buttons[ $name ], $url ); + } else { + return ''; + } + }, $this->template ); + + return ( ! empty( $content ) ? + '
' . + '  ' . + Yii::t( 'app', 'With selected' ) . '  ' . + $content . + '
' : + '' + ); + } + + /** + * Creates a URL for the given action and model. + * This method is called for each button and each row. + * + * @param string $action the button name (or action ID) + * + * @return string the created URL + */ + public function createUrl( $action ) { + if ( is_callable( $this->urlCreator ) ) { + return call_user_func( $this->urlCreator, $action, $this ); + } else { + $params = [ $this->controller ? $this->controller . '/' . $action : $action ]; + + return Url::toRoute( $params ); + } } -} -?> +} \ No newline at end of file diff --git a/src/assets/ModalRemote.js b/src/assets/ModalRemote.js index 0b7b616..1a655d0 100644 --- a/src/assets/ModalRemote.js +++ b/src/assets/ModalRemote.js @@ -213,13 +213,24 @@ function ModalRemote(modalId) { */ function successRemoteResponse(response) { + // Close modal then redirect to a new url if response contains forceRedirect field + if (response.forceRedirect !== undefined && response.forceRedirect) { + this.hide(); + window.location.href = response.forceRedirect; + return; + } + // 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'}); + if(document.querySelector('#crud-datatable-pjax') !== null) { + $.pjax.reload({container: '#crud-datatable-pjax'}); + } } else { - $.pjax.reload({container: response.forceReload}); + if(document.querySelector(response.forceReload) !== null) { + $.pjax.reload({container: response.forceReload}); + } } } diff --git a/src/assets/ModalRemote.min.js b/src/assets/ModalRemote.min.js index 1acddb2..d75fbea 100644 --- a/src/assets/ModalRemote.min.js +++ b/src/assets/ModalRemote.min.js @@ -1 +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); +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){if(void 0!==t.forceRedirect&&t.forceRedirect)return this.hide(),void(window.location.href=t.forceRedirect);void 0!==t.forceReload&&t.forceReload&&("true"==t.forceReload?null!==document.querySelector("#crud-datatable-pjax")&&$.pjax.reload({container:"#crud-datatable-pjax"}):null!==document.querySelector(t.forceReload)&&$.pjax.reload({container:t.forceReload})),void 0!==t.forceClose&&t.forceClose?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 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 r=this;!1!==e&&this.addFooterButton(void 0===e?this.defaults.okLabel:e,"submit","btn btn-primary",function(t){var o;window.FormData?(o=new FormData($("#ModalRemoteConfirmForm")[0]),void 0!==d&&d&&o.append("pks",d.join())):(o=$("#ModalRemoteConfirmForm"),void 0!==d&&d&&(o.pks=d),o=o.serializeArray()),r.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-alert")&&$(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); \ No newline at end of file