diff --git a/CHANGE.md b/CHANGE.md index 6a06605..95d2054 100644 --- a/CHANGE.md +++ b/CHANGE.md @@ -3,10 +3,16 @@ Change Log: `yii2-detail-view` ## Version 1.7.4 -**Date:** 18-Sep-2015 +**Date:** 11-Jan-2016 - (bug #86): Fix Inflector class dependency. - (enh #87): Add ability to show values as not set when empty. +- (enh #89): Fix documentation for type to correct constant. +- (enh #93): Add Polish translations. +- (enh #97): Enhance widget to parse visible attributes correctly. +- (enh #98): CSS Styling enhancements for `table-condensed`. +- (enh #101): Enhance plugin and widget with destroy method. +- (enh #102): Enhancements for PJAX reinitialization. Complements enhancements in kartik-v/yii2-krajee-base#52 and kartik-v/yii2-krajee-base#53. ## Version 1.7.3 diff --git a/DetailView.php b/DetailView.php index e613892..8961115 100644 --- a/DetailView.php +++ b/DetailView.php @@ -3,13 +3,14 @@ /** * @package yii2-detail-view * @author Kartik Visweswaran - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 + * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016 * @version 1.7.4 */ namespace kartik\detail; use Yii; +use yii\base\Arrayable; use yii\base\InvalidConfigException; use yii\bootstrap\Alert; use yii\base\Model; @@ -22,10 +23,9 @@ use kartik\base\TranslationTrait; /** - * Enhances the Yii DetailView widget with various options to include Bootstrap - * specific styling enhancements. Also allows to simply disable Bootstrap styling - * by setting `bootstrap` to false. In addition, it allows you to directly edit - * the detail grid data using a form. + * Enhances the Yii DetailView widget with various options to include Bootstrap specific styling enhancements. Also + * allows to simply disable Bootstrap styling by setting `bootstrap` to false. In addition, it allows you to directly + * edit the detail grid data using a form. * * @author Kartik Visweswaran * @since 1.0 @@ -132,8 +132,7 @@ class DetailView extends \yii\widgets\DetailView public $mode = self::MODE_VIEW; /** - * @var integer the fade animation delay in microseconds when - * toggling between the view and edit modes. + * @var integer the fade animation delay in microseconds when toggling between the view and edit modes. */ public $fadeDelay = 800; @@ -173,32 +172,29 @@ class DetailView extends \yii\widgets\DetailView public $notSetIfEmpty = false; /** - * @var array the HTML attributes for the alert block container which will display - * any alert messages received on update or delete of record. - * This will not be displayed if there are no alert messages. + * @var array the HTML attributes for the alert block container which will display any alert messages received on + * update or delete of record. This will not be displayed if there are no alert messages. */ public $alertContainerOptions = []; /** * @var array the widget settings for each bootstrap alert displayed in the alert container block. * The CSS class in `options` within this will be auto derived and appended. - * - For `update` error messages will be displayed if you have set messages using - * Yii::$app->session->setFlash. The CSS class for the error block will be - * auto-derived based on flash message type using `alertMessageSettings`. - * - For `delete` this will be displayed based on the ajax response. The ajax response - * should be an object that contain the following: + * - For `update` error messages will be displayed if you have set messages using Yii::$app->session->setFlash. The + * CSS class for the error block will be auto-derived based on flash message type using `alertMessageSettings`. + * - For `delete` this will be displayed based on the ajax response. The ajax response should be an object that + * contain the following: * - success: `boolean`, whether the ajax delete is successful. - * - messages: `array|object`,the list of messages to display as key value pairs. - * The key must be one of the message keys in the `alertMessageSettings`, and the - * value must be the message content to be displayed. + * - messages: `array|object`,the list of messages to display as key value pairs. The key must be one of the + * message keys in the `alertMessageSettings`, and the value must be the message content to be displayed. */ public $alertWidgetOptions = []; /** * @var array the flash message settings which will be set as $key => $value, where * - `$key`: flash message key e.g. `error`, `success`. - * - `$value`: CSS class for the flash message e.g. `alert alert-danger`, `alert alert-success`. - * This defaults to the following setting: + * - `$value`: CSS class for the flash message e.g. `alert alert-danger`, `alert alert-success`. This defaults to + * the following setting: * ``` * [ * 'kv-detail-error' => 'alert alert-danger', @@ -221,32 +217,32 @@ class DetailView extends \yii\widgets\DetailView public $bootstrap = true; /** - * @var bool whether the grid table will have a `bordered` style. - * Applicable only if `bootstrap` is `true`. Defaults to `true`. + * @var bool whether the grid table will have a `bordered` style. Applicable only if `bootstrap` is `true`. + * Defaults to `true`. */ public $bordered = true; /** - * @var bool whether the grid table will have a `striped` style. - * Applicable only if `bootstrap` is `true`. Defaults to `true`. + * @var bool whether the grid table will have a `striped` style. Applicable only if `bootstrap` is `true`. Defaults + * to `true`. */ public $striped = true; /** - * @var bool whether the grid table will have a `condensed` style. - * Applicable only if `bootstrap` is `true`. Defaults to `false`. + * @var bool whether the grid table will have a `condensed` style. Applicable only if `bootstrap` is `true`. + * Defaults to `false`. */ public $condensed = false; /** - * @var bool whether the grid table will have a `responsive` style. - * Applicable only if `bootstrap` is `true`. Defaults to `true`. + * @var bool whether the grid table will have a `responsive` style. Applicable only if `bootstrap` is `true`. + * Defaults to `true`. */ public $responsive = true; /** - * @var bool whether the grid table will highlight row on `hover`. - * Applicable only if `bootstrap` is `true`. Defaults to `false`. + * @var bool whether the grid table will highlight row on `hover`. Applicable only if `bootstrap` is `true`. + * Defaults to `false`. */ public $hover = false; @@ -266,66 +262,55 @@ class DetailView extends \yii\widgets\DetailView public $tooltips = true; /** - * @var array a list of attributes to be displayed in the detail view. Each - * array element represents the specification for displaying one particular - * attribute. + * @var array a list of attributes to be displayed in the detail view. Each array element represents the + * specification for displaying one particular attribute. * - * An attribute can be specified as a string in the format of "attribute", - * "attribute:format" or "attribute:format:label", where "attribute" refers - * to the attribute name, and "format" represents the format of the attribute. - * The "format" is passed to the [[Formatter::format()]] method to format an - * attribute value into a displayable text. Please refer to [[Formatter]] for - * the supported types. Both "format" and "label" are optional. They will take - * default values if absent. + * An attribute can be specified as a string in the format of "attribute", "attribute:format" or + * "attribute:format:label", where "attribute" refers to the attribute name, and "format" represents the format + * of the attribute. The "format" is passed to the [[Formatter::format()]] method to format an attribute value + * into a displayable text. Please refer to [[Formatter]] for the supported types. Both "format" and "label" + * are optional. They will take default values if absent. * - * An attribute can also be specified in terms of an array with the following - * elements: + * An attribute can also be specified in terms of an array with the following elements: * - * - attribute: the attribute name. This is required if either "label" or - * "value" is not specified. - * - label: the label associated with the attribute. If this is not specified, - * it will be generated from the attribute name. - * - value: the value to be displayed. If this is not specified, it will be - * retrieved from [[model]] using the attribute name by calling - * [[ArrayHelper::getValue()]]. Note that this value will be formatted into - * a displayable text according to the "format" option. - * - format: the type of the value that determines how the value would be - * formatted into a displayable text. Please refer to [[Formatter]] for - * supported types. - * - visible: whether the attribute is visible. If set to `false`, the - * attribute will NOT be displayed. + * - attribute: the attribute name. This is required if either "label" or "value" is not specified. + * - label: the label associated with the attribute. If this is not specified, it will be generated from the + * attribute name. + * - value: the value to be displayed. If this is not specified, it will be retrieved from [[model]] using the + * attribute name by calling [[ArrayHelper::getValue()]]. Note that this value will be formatted into a + * displayable text according to the "format" option. + * - format: the type of the value that determines how the value would be formatted into a displayable text. Please + * refer to [[Formatter]] for supported types. + * - visible: whether the attribute is visible. If set to `false`, the attribute will NOT be displayed. * * Additional special settings are: - * - viewModel: Model, the model to be used for this attribute in VIEW mode. This will override - * the `model` setting at the widget level. If not set, the widget `model` setting will be used. - * - editModel: Model, the model to be used for this attribute in EDIT mode. This will override - * the `model` setting at the widget level. If not set, the widget `model` setting will be used. - * - rowOptions: array, HTML attributes for the row (if not set, this will be defaulted to the - * `rowOptions` set at the widget level) - * - labelColOptions: array, HTML attributes for the label column (if not set, this will be - * defaulted to the `labelColOptions` set at the widget level) - * - valueColOptions: array, HTML attributes for the value column (if not set, this will be - * defaulted to `valueColOptions` set at the widget level) + * - viewModel: Model, the model to be used for this attribute in VIEW mode. This will override the `model` setting + * at the widget level. If not set, the widget `model` setting will be used. + * - editModel: Model, the model to be used for this attribute in EDIT mode. This will override the `model` setting + * at the widget level. If not set, the widget `model` setting will be used. + * - rowOptions: array, HTML attributes for the row (if not set, this will be defaulted to the `rowOptions` set at + * the widget level) + * - labelColOptions: array, HTML attributes for the label column (if not set, this will be defaulted to the + * `labelColOptions` set at the widget level) + * - valueColOptions: array, HTML attributes for the value column (if not set, this will be defaulted to + * `valueColOptions` set at the widget level) * - group: bool, whether to group the selection by merging the label and value into a single column. * - groupOptions: array, HTML attributes for the grouped/merged column when `group` is set to `true`. - * - type: string, the input type for rendering the attribute in edit mode. - * Must be one of the [[DetailView::::INPUT_]] constants. - * - displayOnly: boolean, if the input is to be set to as `display only` - * in edit mode. - * - widgetOptions: array, the widget options if you set `type` to - * [[DetailView::::INPUT_WIDGET]]. The following special options are recognized: + * - type: string, the input type for rendering the attribute in edit mode. Must be one of the + * [[DetailView::::INPUT_]] constants. + * - displayOnly: boolean, if the input is to be set to as `display only` in edit mode. + * - widgetOptions: array, the widget options if you set `type` to [[DetailView::::INPUT_WIDGET]]. The following + * special options are recognized: * - `class`: string the fully namespaced widget class. - * - items: array, the list of data items for dropDownList, listBox, - * checkboxList & radioList - * - inputType: string, the HTML 5 input type if `type` is set to - * [[DetailView::::INPUT_HTML 5]]. + * - items: array, the list of data items for dropDownList, listBox, checkboxList & radioList + * - inputType: string, the HTML 5 input type if `type` is set to [[DetailView::::INPUT_HTML 5]]. * - inputContainer: array, HTML attributes for the input container - * - inputWidth: string, the width of the container holding the input, should be appended - * along with the width unit (`px` or `%`) - this property is deprecated since v1.7.4 + * - inputWidth: string, the width of the container holding the input, should be appended along with the width unit + * (`px` or `%`) - this property is deprecated since v1.7.4 * - fieldConfig: array, optional, the Active field configuration. * - options: array, optional, the HTML attributes for the input. - * - updateAttr: string, optional, the name of the attribute to be updated, - * when in edit mode. This will default to the the `attribute` setting. + * - updateAttr: string, optional, the name of the attribute to be updated, when in edit mode. This will default to + * the `attribute` setting. */ public $attributes; @@ -333,7 +318,7 @@ class DetailView extends \yii\widgets\DetailView * @var array the options for the ActiveForm that will be generated in edit mode. */ public $formOptions = []; - + /** * @var string the ActiveForm widget class */ @@ -343,34 +328,30 @@ class DetailView extends \yii\widgets\DetailView * @var array the panel settings. If this is set, the grid widget * will be embedded in a bootstrap panel. Applicable only if `bootstrap` * is `true`. The following array keys are supported: - * - `heading`: string | boolean, the panel heading title value. If set to false, - * the entire heading will be not displayed. Note that the `{title}` tag in the - * `headingOptions['template']` will be replaced with this value. - * - `headingOptions`: array, the HTML attributes for the panel heading. Defaults - * to `['class'=>'panel-title']`. The following additional options are available: + * - `heading`: string | boolean, the panel heading title value. If set to false, the entire heading will be not + * displayed. Note that the `{title}` tag in the `headingOptions['template']` will be replaced with this value. + * - `headingOptions`: array, the HTML attributes for the panel heading. Defaults to `['class'=>'panel-title']`. + * The following additional options are available: * - `tag`: string, the tag to render the heading. Defaults to `h3`. - * - `template`: string, the template to render the heading. Defaults to `{buttons}{title}`, - * where: + * - `template`: string, the template to render the heading. Defaults to `{buttons}{title}`, where: * - `{title}` will be replaced with the `heading` value, and * -`{buttons}` will be replaced by the rendered buttons. - * - `type`: string, the panel contextual type (one of the TYPE constants, - * if not set will default to `default` or `self::TYPE_DEFAULT`), - * - `footer`: string | boolean, the panel footer title value. Defaults to `false`. If set to false, - * the entire footer will be not displayed. Note that the `{title}` tag in the - * `footerOptions['template']` will be replaced with this value. - * - `footerOptions`: array, the HTML attributes for the panel footer. Defaults - * to `['class'=>'panel-title']`. The following additional options are available: + * - `type`: string, the panel contextual type (one of the TYPE constants, if not set will default to `default` or + * `self::TYPE_DEFAULT`) + * - `footer`: string | boolean, the panel footer title value. Defaults to `false`. If set to false, the entire + * footer will be not displayed. Note that the `{title}` tag in the `footerOptions['template']` will be + * replaced with this value. + * - `footerOptions`: array, the HTML attributes for the panel footer. Defaults to `['class'=>'panel-title']`. The + * following additional options are available: * - `tag`: string, the tag to render the footer. Defaults to `h4`. - * - `template`: string, the template to render the footer. Defaults to `{title}`, - * where: + * - `template`: string, the template to render the footer. Defaults to `{title}`, where: * - `{title}` will be replaced with the `footer`, and * -`{buttons}` will be replaced by the rendered buttons. */ public $panel = []; /** - * @var string the main template to render the detail view. The following - * tags will be replaced: + * @var string the main template to render the detail view. The following tags will be replaced: * - `{detail}`: will be replaced by the rendered detail view * - `{buttons}`: the buttons to be displayed as set in `buttons1` and * `buttons2`. @@ -383,8 +364,7 @@ class DetailView extends \yii\widgets\DetailView public $buttonContainer = ['class' => 'pull-right']; /** - * @var string the buttons to show when in view mode. The following - * tags will be replaced: + * @var string the buttons to show when in view mode. The following tags will be replaced: * - `{view}`: the view button * - `{update}`: the update button * - `{delete}`: the delete button @@ -394,8 +374,7 @@ class DetailView extends \yii\widgets\DetailView public $buttons1 = '{update} {delete}'; /** - * @var string the buttons template to show when in edit mode. The - * following tags will be replaced: + * @var string the buttons template to show when in edit mode. The following tags will be replaced: * - `{view}`: the view button * - `{update}`: the update button * - `{reset}`: the reset button @@ -406,8 +385,7 @@ class DetailView extends \yii\widgets\DetailView public $buttons2 = '{view} {reset} {save}'; /** - * @var array the HTML attributes for the container displaying the - * VIEW mode attributes. + * @var array the HTML attributes for the container displaying the VIEW mode attributes. */ public $viewAttributeContainer = []; @@ -427,24 +405,24 @@ class DetailView extends \yii\widgets\DetailView public $editButtonsContainer = []; /** - * @var array the HTML attributes for the view button. This will toggle the view from edit mode to view mode. - * The following special options are recognized: + * @var array the HTML attributes for the view button. This will toggle the view from edit mode to view mode. The + * following special options are recognized: * - `label`: the save button label. This will not be HTML encoded. * Defaults to ''. */ public $viewOptions = []; /** - * @var array the HTML attributes for the update button. This button will toggle the edit mode on. - * The following special options are recognized: + * @var array the HTML attributes for the update button. This button will toggle the edit mode on. The following + * special options are recognized: * - `label`: the update button label. This will not be HTML encoded. * Defaults to ''. */ public $updateOptions = []; /** - * @var array the HTML attributes for the reset button. This button will reset the form in edit mode. - * The following special options are recognized: + * @var array the HTML attributes for the reset button. This button will reset the form in edit mode. The following + * special options are recognized: * - `label`: the reset button label. This will not be HTML encoded. * Defaults to ''. */ @@ -452,15 +430,15 @@ class DetailView extends \yii\widgets\DetailView /** * @var array the HTML attributes for the edit button. The following special options are recognized: - * - `label`: the delete button label. This will not be HTML encoded. - * Defaults to ''. + * - `label`: the delete button label. This will not be HTML encoded. Defaults to ''. * - `url`: the delete button url. If not set will default to `#`. - * - `params`: array, the parameters to be passed via ajax which you must set as key value pairs. This - * will be automatically json encoded, so you can set JsExpression or callback + * - `params`: array, the parameters to be passed via ajax which you must set as key value pairs. This will be + * automatically json encoded, so you can set JsExpression or callback * - `ajaxSettings`: array, the ajax settings if you choose to override the delete ajax settings. * @see http://api.jquery.com/jquery.ajax/ - * - `confirm': string, the confirmation message before triggering delete. Defaults to - * Yii::t('kvdetail', 'Are you sure you want to delete this item?') + * - `confirm': string, the confirmation message before triggering delete. Defaults to Yii::t('kvdetail', 'Are you + * sure you want to delete this item?') * - `showErrorStack`: boolean, whether to show the complete error stack. */ public $deleteOptions = []; @@ -468,8 +446,8 @@ class DetailView extends \yii\widgets\DetailView /** * @var array the HTML attributes for the save button. This will default to a form submit button. * The following special options are recognized: - * - `label`: the save button label. This will not be HTML encoded. - * Defaults to ''. + * - `label`: the save button label. This will not be HTML encoded. Defaults to ''. */ public $saveOptions = []; @@ -484,19 +462,24 @@ class DetailView extends \yii\widgets\DetailView public $i18n = []; /** - * @var array the `kvDetailView` plugin options + * @var string the Detail View plugin name + */ + public $pluginName = 'kvDetailView'; + + /** + * @var array the Detail View plugin options */ public $pluginOptions = []; /** - * @var string translation message file category name for i18n + * @var string the Detail View plugin destroy JS */ - protected $_msgCat = 'kvdetail'; + public $pluginDestroyJs = ''; /** - * @var string the name of the jQuery plugin + * @var string translation message file category name for i18n */ - protected $_pluginName = 'kvDetailView'; + protected $_msgCat = 'kvdetail'; /** * @var string the hashed global variable name storing the pluginOptions @@ -517,12 +500,12 @@ class DetailView extends \yii\widgets\DetailView * @var ActiveForm the form instance */ protected $_form; - + /** * @var array HTML attributes for child tables */ protected $_childTableOptions = []; - + /** * @var array HTML attributes for table row */ @@ -533,8 +516,30 @@ class DetailView extends \yii\widgets\DetailView */ public function init() { + $this->initWidget(); + parent:: init(); + } + + /** + * @inheritdoc + */ + public function run() + { + $this->runWidget(); + } + + /** + * Initializes the detail view widget + * + * @throws InvalidConfigException + */ + protected function initWidget() + { + /** + * @var ActiveForm $formClass + */ + $formClass = $this->formClass; if ($this->enableEditMode) { - $formClass = $this->formClass; $activeForm = ActiveForm::classname(); if (!is_subclass_of($formClass, $activeForm) && $formClass !== $activeForm) { throw new InvalidConfigException("Form class '{$formClass}' must exist and extend from '{$activeForm}'."); @@ -560,7 +565,17 @@ public function init() Html::addCssClass($this->_childTableOptions, 'kv-child-table'); Html::addCssClass($this->options, 'detail-view'); Html::addCssStyle($this->labelColOptions, "text-align:{$this->hAlign};vertical-align:{$this->vAlign};"); - parent:: init(); + } + + /** + * Prepares and runs the detail view widget + */ + protected function runWidget() + { + /** + * @var ActiveForm $formClass + */ + $formClass = $this->formClass; if (empty($this->container['id'])) { $this->container['id'] = $this->getId(); } @@ -577,13 +592,6 @@ public function init() 'kv-detail-warning' => 'alert alert-warning' ]; $this->registerAssets(); - } - - /** - * @inheritdoc - */ - public function run() - { $output = $this->renderDetailView(); if (is_array($this->panel) && !empty($this->panel) && $this->panel !== false) { $output = $this->renderPanel($output); @@ -597,10 +605,9 @@ public function run() if ($this->enableEditMode) { Html::addCssClass($this->editButtonsContainer, 'kv-buttons-2'); $buttons .= Html::tag('span', $this->renderButtons(2), $this->editButtonsContainer); - } + } echo str_replace('{buttons}', Html::tag('div', $buttons, $this->buttonContainer), $output); if ($this->enableEditMode) { - $formClass = $this->formClass; $formClass::end(); } } @@ -684,7 +691,6 @@ protected function validateDisplay() protected function renderDetailView() { $rows = []; - $i = 0; foreach ($this->attributes as $attribute) { $rows[] = $this->renderAttributeRow($attribute); } @@ -704,7 +710,6 @@ protected function renderDetailView() */ protected function renderAttributeRow($attribute) { - $content = ''; $this->_rowOptions = ArrayHelper::getValue($attribute, 'rowOptions', $this->rowOptions); if (isset($attribute['columns'])) { Html::addCssClass($this->_rowOptions, 'kv-child-table-row'); @@ -712,8 +717,8 @@ protected function renderAttributeRow($attribute) if (!empty($child['attribute'])) { $childName = $child['attribute']; if (!isset($child['label'])) { - $child['label'] = $this->model instanceof Model ? - $this->model->getAttributeLabel($childName) : + $child['label'] = $this->model instanceof Model ? + $this->model->getAttributeLabel($childName) : Inflector::camel2words($childName, true); } if (!array_key_exists('value', $child)) { @@ -736,11 +741,11 @@ protected function renderAttributeRow($attribute) * @param array $attribute the specification of the attribute to be rendered. * * @return string the rendering result - */ + */ protected function renderAttributeItem($attribute) { - $labelColOptions = ArrayHelper::getValue($attribute, 'labelColOptions', $this->labelColOptions); - $valueColOptions = ArrayHelper::getValue($attribute, 'valueColOptions', $this->valueColOptions); + $labelColOpts = ArrayHelper::getValue($attribute, 'labelColOptions', $this->labelColOptions); + $valueColOpts = ArrayHelper::getValue($attribute, 'valueColOptions', $this->valueColOptions); if (ArrayHelper::getValue($attribute, 'group', false)) { $groupOptions = ArrayHelper::getValue($attribute, 'groupOptions', []); $label = ArrayHelper::getValue($attribute, 'label', ''); @@ -759,16 +764,16 @@ protected function renderAttributeItem($attribute) if ($this->notSetIfEmpty && ($value === '' || $value === null)) { $value = null; } - $dispAttr = $this->formatter->format($value, $attribute['format']); Html::addCssClass($this->viewAttributeContainer, 'kv-attribute'); + $dispAttr = $this->formatter->format($value, $attribute['format']); + Html::addCssClass($this->viewAttributeContainer, 'kv-attribute'); Html::addCssClass($this->editAttributeContainer, 'kv-form-attribute'); $output = Html::tag('div', $dispAttr, $this->viewAttributeContainer) . "\n"; if ($this->enableEditMode) { - $editInput = !empty($attribute['displayOnly']) && $attribute['displayOnly'] ? - $dispAttr : + $editInput = ArrayHelper::getValue($attribute, 'displayOnly', false) ? $dispAttr : $this->renderFormAttribute($attribute); $output .= Html::tag('div', $editInput, $this->editAttributeContainer); } - return Html::tag('th', $attribute['label'], $labelColOptions) . "\n" . Html::tag('td', $output, $valueColOptions); + return Html::tag('th', $attribute['label'], $labelColOpts) . "\n" . Html::tag('td', $output, $valueColOpts); } /** @@ -855,12 +860,10 @@ protected function renderFormAttribute($config) $items = ArrayHelper::getValue($config, 'items', []); return $this->_form->field($model, $attr, $fieldConfig)->$input($items, $options); } - if ($input == self::INPUT_HTML5_INPUT) { $inputType = ArrayHelper::getValue($config, 'inputType', self::INPUT_TEXT); return $this->_form->field($model, $attr, $fieldConfig)->$input($inputType, $options); } - return $this->_form->field($model, $attr, $fieldConfig)->$input($options); } @@ -980,17 +983,17 @@ protected function getDefaultButton($type, $icon, $title) $options['data-toggle'] = 'tooltip'; $options['data-container'] = 'body'; } - if ($type === 'reset') { - return Html::resetButton($label, $options); - } elseif ($type === 'save') { - return Html::submitButton($label, $options); - } elseif ($type === 'delete') { - $url = ArrayHelper::remove($options, 'url', '#'); - return Html::a($label, $url, $options); - } else { - $options['type'] = 'button'; - return Html::button($label, $options); + switch ($type) { + case 'reset': + return Html::resetButton($label, $options); + case 'save': + return Html::submitButton($label, $options); + case 'delete': + $url = ArrayHelper::remove($options, 'url', '#'); + return Html::a($label, $url, $options); } + $options['type'] = 'button'; + return Html::button($label, $options); } /** @@ -1031,15 +1034,16 @@ protected function registerAssets() if ($this->enableEditMode) { $options['mode'] = $this->mode; } - $this->registerPlugin($this->_pluginName, $id); + $this->registerPlugin($this->pluginName, $id); if ($this->tooltips) { $view->registerAssetBundle('yii\bootstrap\BootstrapPluginAsset'); $view->registerJs($id . '.find("[data-toggle=tooltip]").tooltip();'); } } - + /** * Normalizes the attribute specifications. + * * @throws InvalidConfigException */ protected function normalizeAttributes() @@ -1048,7 +1052,8 @@ protected function normalizeAttributes() if ($this->model instanceof Model) { $this->attributes = $this->model->attributes(); } elseif (is_object($this->model)) { - $this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : array_keys(get_object_vars($this->model)); + $this->attributes = $this->model instanceof Arrayable ? $this->model->toArray() : + array_keys(get_object_vars($this->model)); } elseif (is_array($this->model)) { $this->attributes = array_keys($this->model); } else { @@ -1057,23 +1062,29 @@ protected function normalizeAttributes() sort($this->attributes); } foreach ($this->attributes as $i => $attribute) { - $this->attributes[$i] = $this->parseAttributeItem($attribute, $i); + $this->attributes[$i] = $this->parseAttributeItem($attribute); + if (isset($attribute['visible']) && !$attribute['visible']) { + unset($this->attributes[$i]); + } } - // die('
'.print_r($this->attributes, true) . '
'); } - + /** * Parses and returns the attribute + * * @param string|array $attribute the attribute item configuration - * @param int $i the zero-based index - * @param bool $child whether this is a child attribute + * * @return array the parsed attribute item configuration + * @throws InvalidConfigException */ - protected function parseAttributeItem($attribute, $i, $child = false) + protected function parseAttributeItem($attribute) { if (is_string($attribute)) { if (!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/', $attribute, $matches)) { - throw new InvalidConfigException('The attribute must be specified in the format of "attribute", "attribute:format" or "attribute:format:label"'); + throw new InvalidConfigException( + 'The attribute must be specified in the format of "attribute", "attribute:format" or ' . + '"attribute:format:label"' + ); } $attribute = [ 'attribute' => $matches[1], @@ -1086,7 +1097,7 @@ protected function parseAttributeItem($attribute, $i, $child = false) } if (isset($attribute['columns'])) { foreach ($attribute['columns'] as $j => $child) { - $attr = $this->parseAttributeItem($child, $j, true); + $attr = $this->parseAttributeItem($child); if (isset($attr['visible']) && !$attr['visible']) { unset($attribute['columns'][$j]); continue; @@ -1095,36 +1106,29 @@ protected function parseAttributeItem($attribute, $i, $child = false) } return $attribute; } - if (!empty($attribute['updateAttr'])) { - $attrib = $attribute['updateAttr']; - if (ctype_alnum(str_replace('_', '', $attrib))) { - return; - } else { - throw new InvalidConfigException("The 'updateAttr' name '{$attrib}' is invalid."); - } + $attr = ArrayHelper::getValue($attribute, 'updateAttr'); + if ($attr && !ctype_alnum(str_replace('_', '', $attr))) { + throw new InvalidConfigException("The 'updateAttr' name '{$attr}' is invalid."); } - $attrib = ArrayHelper::getValue($attribute, 'attribute', ''); - if ($attrib && strpos($attrib, '.') !== false) { + $attr = ArrayHelper::getValue($attribute, 'attribute', ''); + if ($attr && strpos($attr, '.') !== false) { throw new InvalidConfigException( - "The attribute '{$attrib}' is invalid. You cannot directly pass relational " . - "attributes in string format within '\kartik\widgets\DetailView'. Instead " . - "use the array format with 'attribute' property set to base field, and the " . - "'value' property returning the relational data. You can also override the " . - "widget 'model' settings by setting the 'viewModel' and / or 'editModel' at ". - "the attribute array level." + "The attribute '{$attr}' is invalid. You cannot directly pass relational attributes in string format " . + "within '\\kartik\\widgets\\DetailView'. Instead use the array format with 'attribute' property " . + "set to base field, and the 'value' property returning the relational data. You can also override the " . + "widget 'model' settings by setting the 'viewModel' and / or 'editModel' at the attribute array level." ); } - if (isset($attribute['visible']) && !$attribute['visible'] && !$child) { - unset($this->attributes[$i]); - } if (!isset($attribute['format'])) { $attribute['format'] = 'text'; } if (isset($attribute['attribute'])) { $attributeName = $attribute['attribute']; - $model = !empty($attribute['viewModel']) && $attribute['viewModel'] instanceof Model ? $attribute['viewModel'] : $this->model; + $model = !empty($attribute['viewModel']) && $attribute['viewModel'] instanceof Model ? + $attribute['viewModel'] : $this->model; if (!isset($attribute['label'])) { - $attribute['label'] = $model instanceof Model ? $model->getAttributeLabel($attributeName) : Inflector::camel2words($attributeName, true); + $attribute['label'] = $model instanceof Model ? $model->getAttributeLabel($attributeName) : + Inflector::camel2words($attributeName, true); } if (!array_key_exists('value', $attribute)) { $attribute['value'] = ArrayHelper::getValue($model, $attributeName); @@ -1134,7 +1138,9 @@ protected function parseAttributeItem($attribute, $i, $child = false) $attribute['value'] = ''; return $attribute; } - throw new InvalidConfigException('The attribute configuration requires the "attribute" element to determine the value and display label.'); + throw new InvalidConfigException( + 'The attribute configuration requires the "attribute" element to determine the value and display label.' + ); } return $attribute; } diff --git a/DetailViewAsset.php b/DetailViewAsset.php index 5a88d02..a933589 100644 --- a/DetailViewAsset.php +++ b/DetailViewAsset.php @@ -3,22 +3,24 @@ /** * @package yii2-detail-view * @author Kartik Visweswaran - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 + * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016 * @version 1.7.4 */ namespace kartik\detail; +use kartik\base\AssetBundle; + /** * Asset bundle for DetailView Widget * * @author Kartik Visweswaran * @since 1.0 */ -class DetailViewAsset extends \kartik\base\AssetBundle +class DetailViewAsset extends AssetBundle { /** - * @inherit doc + * @inheritdoc */ public function init() { @@ -27,5 +29,4 @@ public function init() $this->setupAssets('css', ['css/kv-detail-view']); parent::init(); } - -} \ No newline at end of file +} diff --git a/LICENSE.md b/LICENSE.md index f5b9819..047ee53 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -Copyright (c) 2014 - 2015, Kartik Visweswaran +Copyright (c) 2014 - 2016, Kartik Visweswaran Krajee.com All rights reserved. diff --git a/assets/css/kv-detail-view.css b/assets/css/kv-detail-view.css index c247aec..08c80f0 100644 --- a/assets/css/kv-detail-view.css +++ b/assets/css/kv-detail-view.css @@ -1,13 +1,13 @@ /*! * @package yii2-detail-view * @author Kartik Visweswaran - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 + * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016 * @version 1.7.4 * * Styles for yii2-detail-view extension * * Author: Kartik Visweswaran - * Copyright: 2014 - 2015, Kartik Visweswaran, Krajee.com + * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com * For more JQuery plugins visit http://plugins.krajee.com * For more Yii related demos visit http://demos.krajee.com */ @@ -30,13 +30,14 @@ background: #ffffff url('../img/loading.gif') top 15px right 15px no-repeat; } -.kv-detail-loading * { +.kv-detail-loading * { background: transparent !important; } .kv-detail-loading td { border-color: #efefef !important; } + .kv-edit-mode .kv-detail-view { overflow-y: hidden; } @@ -46,7 +47,7 @@ } .kv-edit-mode table { - overflow:hidden; + overflow: hidden; } .kv-child-table { @@ -55,8 +56,8 @@ .kv-child-table-row, .kv-child-table-row > td { vertical-align: middle; - padding: 0!important; - margin: 0!important; + padding: 0 !important; + margin: 0 !important; } .kv-child-table-cell { @@ -66,22 +67,30 @@ overflow: hidden; } +.kv-child-table-row th { + border-left: 1px #ddd solid; + border-right: 1px #ddd solid; +} + .kv-child-table td, .kv-child-table th { margin: 0; + background: transparent; + +} + +.table .kv-child-table > tbody > tr > td, .table .kv-child-table > tbody > tr > th { padding: 8px; - background: transparent; } -.kv-child-table-row th { - border-left: 1px #ddd solid; - border-right: 1px #ddd solid; +.table-condensed .kv-child-table > tbody > tr > td, .table-condensed .kv-child-table > tbody > tr > th { + padding: 5px; } .kv-action-btn { - margin:0 2px; - padding:0 5px; - background:none; - border:none; + margin: 0 2px; + padding: 0 5px; + background: none; + border: none; font-size: 16px; } @@ -90,41 +99,41 @@ } .panel-primary .kv-action-btn { - color:#d9edf7; + color: #d9edf7; } .panel-primary .kv-action-btn:hover { - color:#c4e3f3; + color: #c4e3f3; } .panel-info .kv-action-btn { - color:#31708f; + color: #31708f; } .panel-info .kv-action-btn:hover { - color:#245269; + color: #245269; } .panel-warning .kv-action-btn { - color:#8a6d3b; + color: #8a6d3b; } .panel-warning .kv-action-btn:hover { - color:#66512c; + color: #66512c; } .panel-danger .kv-action-btn { - color:#a94442; + color: #a94442; } .panel-danger .kv-action-btn:hover { - color:#843534; + color: #843534; } .panel-success .kv-action-btn { - color:#3c763d; + color: #3c763d; } .panel-success .kv-action-btn:hover { - color:#2b542c; + color: #2b542c; } diff --git a/assets/css/kv-detail-view.min.css b/assets/css/kv-detail-view.min.css index 064f046..aef1270 100644 --- a/assets/css/kv-detail-view.min.css +++ b/assets/css/kv-detail-view.min.css @@ -1,13 +1,13 @@ /*! * @package yii2-detail-view * @author Kartik Visweswaran - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 + * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016 * @version 1.7.4 * * Styles for yii2-detail-view extension * * Author: Kartik Visweswaran - * Copyright: 2014 - 2015, Kartik Visweswaran, Krajee.com + * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com * For more JQuery plugins visit http://plugins.krajee.com * For more Yii related demos visit http://demos.krajee.com - */.kv-form-attribute .help-block{margin-bottom:-15px}.kv-edit-mode .kv-edit-hidden,.kv-view-mode .kv-view-hidden{display:none}.kv-edit-mode .kv-view-hidden,.kv-view-mode .kv-edit-hidden{display:table-row}.kv-detail-loading{opacity:.3;background:url(../img/loading.gif)top 15px right 15px no-repeat #fff}.kv-detail-loading *{background:0 0!important}.kv-detail-loading td{border-color:#efefef!important}.kv-edit-mode .kv-detail-view{overflow-y:hidden}.kv-form-attribute .row{margin-bottom:-5px}.kv-edit-mode table{overflow:hidden}.kv-child-table{width:100%}.kv-child-table-row,.kv-child-table-row>td{vertical-align:middle;padding:0!important;margin:0!important}.kv-child-table-cell{margin:0;padding:0;width:100%;overflow:hidden}.kv-child-table td,.kv-child-table th{margin:0;padding:8px;background:0 0}.kv-child-table-row th{border-left:1px #ddd solid;border-right:1px #ddd solid}.kv-action-btn{margin:0 2px;padding:0 5px;background:0 0;border:none;font-size:16px}.kv-action-btn:focus{outline:0}.panel-primary .kv-action-btn{color:#d9edf7}.panel-primary .kv-action-btn:hover{color:#c4e3f3}.panel-info .kv-action-btn{color:#31708f}.panel-info .kv-action-btn:hover{color:#245269}.panel-warning .kv-action-btn{color:#8a6d3b}.panel-warning .kv-action-btn:hover{color:#66512c}.panel-danger .kv-action-btn{color:#a94442}.panel-danger .kv-action-btn:hover{color:#843534}.panel-success .kv-action-btn{color:#3c763d}.panel-success .kv-action-btn:hover{color:#2b542c} \ No newline at end of file + */.kv-form-attribute .help-block{margin-bottom:-15px}.kv-edit-mode .kv-edit-hidden,.kv-view-mode .kv-view-hidden{display:none}.kv-edit-mode .kv-view-hidden,.kv-view-mode .kv-edit-hidden{display:table-row}.kv-detail-loading{opacity:.3;background:url(../img/loading.gif)top 15px right 15px no-repeat #fff}.kv-detail-loading *{background:0 0!important}.kv-detail-loading td{border-color:#efefef!important}.kv-edit-mode .kv-detail-view{overflow-y:hidden}.kv-form-attribute .row{margin-bottom:-5px}.kv-edit-mode table{overflow:hidden}.kv-child-table{width:100%}.kv-child-table-row,.kv-child-table-row>td{vertical-align:middle;padding:0!important;margin:0!important}.kv-child-table-cell{margin:0;padding:0;width:100%;overflow:hidden}.kv-child-table-row th{border-left:1px #ddd solid;border-right:1px #ddd solid}.kv-child-table td,.kv-child-table th{margin:0;background:0 0}.table .kv-child-table>tbody>tr>td,.table .kv-child-table>tbody>tr>th{padding:8px}.table-condensed .kv-child-table>tbody>tr>td,.table-condensed .kv-child-table>tbody>tr>th{padding:5px}.kv-action-btn{margin:0 2px;padding:0 5px;background:0 0;border:none;font-size:16px}.kv-action-btn:focus{outline:0}.panel-primary .kv-action-btn{color:#d9edf7}.panel-primary .kv-action-btn:hover{color:#c4e3f3}.panel-info .kv-action-btn{color:#31708f}.panel-info .kv-action-btn:hover{color:#245269}.panel-warning .kv-action-btn{color:#8a6d3b}.panel-warning .kv-action-btn:hover{color:#66512c}.panel-danger .kv-action-btn{color:#a94442}.panel-danger .kv-action-btn:hover{color:#843534}.panel-success .kv-action-btn{color:#3c763d}.panel-success .kv-action-btn:hover{color:#2b542c} \ No newline at end of file diff --git a/assets/js/kv-detail-view.js b/assets/js/kv-detail-view.js index a272b5f..13adb9c 100644 --- a/assets/js/kv-detail-view.js +++ b/assets/js/kv-detail-view.js @@ -1,20 +1,26 @@ /*! * @package yii2-detail-view * @author Kartik Visweswaran - * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015 + * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016 * @version 1.7.4 * * Client extension for the yii2-detail-view extension * * Author: Kartik Visweswaran - * Copyright: 2014 - 2015, Kartik Visweswaran, Krajee.com + * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com * For more JQuery plugins visit http://plugins.krajee.com * For more Yii related demos visit http://demos.krajee.com */ - (function ($) { "use strict"; + var NAMESPACE, events; + NAMESPACE = '.kvDetailView'; + events = { + click: 'click' + NAMESPACE, + afterValidate: 'afterValidate' + NAMESPACE + }; + var KvDetailView = function (element, options) { var self = this; self.$element = $(element); @@ -31,7 +37,7 @@ self.initElements(); self.listen(); }, - alert: function(type, msg) { + alert: function (type, msg) { var self = this, css; css = self.alertMessageSettings[type]; if (msg) { @@ -41,41 +47,50 @@ return ''; }, initAlert: function () { - var self = this, $alert = self.$element.find('.kv-alert-container'); - $alert.find('.alert .close').each(function() { + var self = this, $alert = self.$element.find('.kv-alert-container'), eClick = events.click; + $alert.find('.alert .close').each(function () { var $el = $(this); - $el.off('click').on('click', function() { - setTimeout(function() { - if (!$alert.find('.alert').length) { + $el.off(eClick).on(eClick, function () { + setTimeout(function () { + if (!$alert.find('.alert').length) { $alert.hide(); } }, 300); }); - }); + }); + }, + destroy: function () { + var self = this, eClick = events.click, eValidate = events.afterValidate; + self.$btnSave.off(eClick); + self.$btnUpdate.off(eClick); + self.$btnView.off(eClick); + self.$btnDelete.off(eClick); + self.$element.find('.kv-detail-view').closest('form').off(eValidate); + self.$element.find('.kv-alert-container .alert .close').off(eClick); }, listen: function () { - var self = this, $alert = self.$element.find('.kv-alert-container'), - $detail = self.$element.find('.kv-detail-view'); - $detail.closest('form').on('afterValidate', function (event, messages) { + var self = this, $alert = self.$element.find('.kv-alert-container'), eClick = events.click, + eValidate = events.afterValidate, $detail = self.$element.find('.kv-detail-view'); + $detail.closest('form').off(eValidate).on(eValidate, function (event, messages) { if (messages !== undefined) { $detail.removeClass('kv-detail-loading'); } }); - self.$btnSave.on('click', function () { + self.$btnSave.off(eClick).on(eClick, function () { $alert.hide(); $detail.removeClass('kv-detail-loading').addClass('kv-detail-loading'); }); - self.$btnUpdate.on('click', function () { + self.$btnUpdate.off(eClick).on(eClick, function () { self.setMode('edit'); }); - self.$btnView.on('click', function () { + self.$btnView.off(eClick).on(eClick, function () { self.setMode('view'); }); - self.$btnDelete.on('click', function (ev) { - var $el = $(this), params = self.deleteParams, confirmMsg = self.deleteConfirm, - settings = self.deleteAjaxSettings || {}; + self.$btnDelete.off(eClick).on(eClick, function (ev) { + var $el = $(this), params = self.deleteParams, confirmMsg = self.deleteConfirm, + settings = self.deleteAjaxSettings || {}; ev.preventDefault(); - if (confirmMsg && !confirm(confirmMsg)) { + if (confirmMsg && !confirm(confirmMsg)) { return; } settings = $.extend({ @@ -83,7 +98,7 @@ dataType: 'json', data: params, url: $el.attr('href'), - beforeSend: function() { + beforeSend: function () { $alert.html('').hide(); $detail.removeClass('kv-detail-loading').addClass('kv-detail-loading'); }, @@ -94,20 +109,21 @@ self.$btnUpdate.attr('disabled', 'disabled'); self.$btnView.attr('disabled', 'disabled'); self.$btnSave.attr('disabled', 'disabled'); - }; - $.each(data.messages, function(key, msg) { + } + $.each(data.messages, function (key, msg) { $alert.append(self.alert(key, msg)); }); - $alert.hide().fadeIn('slow', function() { - $detail.removeClass('kv-detail-loading'); - self.initAlert(); + $alert.hide().fadeIn('slow', function () { + $detail.removeClass('kv-detail-loading'); + self.initAlert(); }); }, - error: function(xhr, txt, err) { + error: function (xhr, txt, err) { var msg = ''; if (self.showErrorStack) { msg = xhr.responseText ? $(xhr.responseText).text() : ''; - msg = msg && msg.length ? '
' + $.trim(msg).replace(/\n\s*\n/g, '\n').replace(/\' : '';
+                            msg = msg && msg.length ? '
' + $.trim(msg).replace(/\n\s*\n/g, '\n')
+                                .replace(/' : '';
                         }
                         msg = self.alert('kv-detail-error', err + msg);
                         $detail.removeClass('kv-detail-loading');
diff --git a/assets/js/kv-detail-view.min.js b/assets/js/kv-detail-view.min.js
index e02a8a9..0eb5452 100644
--- a/assets/js/kv-detail-view.min.js
+++ b/assets/js/kv-detail-view.min.js
@@ -1,13 +1,13 @@
 /*!
  * @package   yii2-detail-view
  * @author    Kartik Visweswaran 
- * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2015
+ * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2014 - 2016
  * @version   1.7.4
  *
  * Client extension for the yii2-detail-view extension
  * 
  * Author: Kartik Visweswaran
- * Copyright: 2014 - 2015, Kartik Visweswaran, Krajee.com
+ * Copyright: 2014 - 2016, Kartik Visweswaran, Krajee.com
  * For more JQuery plugins visit http://plugins.krajee.com
  * For more Yii related demos visit http://demos.krajee.com
- */!function(e){"use strict";var t=function(t,n){var i=this;i.$element=e(t),e.each(n,function(e,t){i[e]=t}),i.init()};t.prototype={constructor:t,init:function(){var e=this;e.initElements(),e.listen()},alert:function(e,t){var n,i=this;return n=i.alertMessageSettings[e],t?(n=n||"alert alert-"+e,i.alertTemplate.replace("{content}",t).replace("{class}",n)):""},initAlert:function(){var t=this,n=t.$element.find(".kv-alert-container");n.find(".alert .close").each(function(){var t=e(this);t.off("click").on("click",function(){setTimeout(function(){n.find(".alert").length||n.hide()},300)})})},listen:function(){var t=this,n=t.$element.find(".kv-alert-container"),i=t.$element.find(".kv-detail-view");i.closest("form").on("afterValidate",function(e,t){void 0!==t&&i.removeClass("kv-detail-loading")}),t.$btnSave.on("click",function(){n.hide(),i.removeClass("kv-detail-loading").addClass("kv-detail-loading")}),t.$btnUpdate.on("click",function(){t.setMode("edit")}),t.$btnView.on("click",function(){t.setMode("view")}),t.$btnDelete.on("click",function(a){var l=e(this),d=t.deleteParams,s=t.deleteConfirm,o=t.deleteAjaxSettings||{};a.preventDefault(),(!s||confirm(s))&&(o=e.extend({type:"post",dataType:"json",data:d,url:l.attr("href"),beforeSend:function(){n.html("").hide(),i.removeClass("kv-detail-loading").addClass("kv-detail-loading")},success:function(a){a.success&&(i.hide(),t.$btnDelete.attr("disabled","disabled"),t.$btnUpdate.attr("disabled","disabled"),t.$btnView.attr("disabled","disabled"),t.$btnSave.attr("disabled","disabled")),e.each(a.messages,function(e,i){n.append(t.alert(e,i))}),n.hide().fadeIn("slow",function(){i.removeClass("kv-detail-loading"),t.initAlert()})},error:function(a,l,d){var s="";t.showErrorStack&&(s=a.responseText?e(a.responseText).text():"",s=s&&s.length?"
"+e.trim(s).replace(/\n\s*\n/g,"\n").replace(/\":""),s=t.alert("kv-detail-error",d+s),i.removeClass("kv-detail-loading"),n.html(s).hide().fadeIn("slow"),t.initAlert()}},o),e.ajax(o))}),t.initAlert()},setMode:function(e){var t=this,n=t.fadeDelay;"edit"===e?(t.$attribs.fadeOut(n,function(){t.$formAttribs.fadeIn(n),t.$element.removeClass("kv-view-mode kv-edit-mode").addClass("kv-edit-mode")}),t.$buttons1.fadeOut(n,function(){t.$buttons2.fadeIn(n)})):(t.$formAttribs.fadeOut(n,function(){t.$attribs.fadeIn(n),t.$element.removeClass("kv-view-mode kv-edit-mode").addClass("kv-view-mode")}),t.$buttons2.fadeOut(n,function(){t.$buttons1.fadeIn(n)}))},initElements:function(){var e=this,t=e.$element;e.$btnUpdate=t.find(".kv-btn-update"),e.$btnDelete=t.find(".kv-btn-delete"),e.$btnView=t.find(".kv-btn-view"),e.$btnSave=t.find(".kv-btn-save"),e.$attribs=t.find(".kv-attribute"),e.$formAttribs=t.find(".kv-form-attribute"),e.$buttons1=t.find(".kv-buttons-1"),e.$buttons2=t.find(".kv-buttons-2")}},e.fn.kvDetailView=function(n){var i=Array.apply(null,arguments);return i.shift(),this.each(function(){var a=e(this),l=a.data("kvDetailView"),d="object"==typeof n&&n;l||(l=new t(this,e.extend({},e.fn.kvDetailView.defaults,d,e(this).data())),a.data("kvDetailView",l)),"string"==typeof n&&l[n].apply(l,i)})},e.fn.kvDetailView.defaults={mode:"view",fadeDelay:800,alertTemplate:"",alertMessageSettings:{},deleteParams:{},deleteAjaxSettings:{},deleteConfirm:"",showErrorStack:!1},e.fn.kvDetailView.Constructor=t}(window.jQuery);
\ No newline at end of file
+ */!function(e){"use strict";var t,n;t=".kvDetailView",n={click:"click"+t,afterValidate:"afterValidate"+t};var i=function(t,n){var i=this;i.$element=e(t),e.each(n,function(e,t){i[e]=t}),i.init()};i.prototype={constructor:i,init:function(){var e=this;e.initElements(),e.listen()},alert:function(e,t){var n,i=this;return n=i.alertMessageSettings[e],t?(n=n||"alert alert-"+e,i.alertTemplate.replace("{content}",t).replace("{class}",n)):""},initAlert:function(){var t=this,i=t.$element.find(".kv-alert-container"),a=n.click;i.find(".alert .close").each(function(){var t=e(this);t.off(a).on(a,function(){setTimeout(function(){i.find(".alert").length||i.hide()},300)})})},destroy:function(){var e=this,t=n.click,i=n.afterValidate;e.$btnSave.off(t),e.$btnUpdate.off(t),e.$btnView.off(t),e.$btnDelete.off(t),e.$element.find(".kv-detail-view").closest("form").off(i),e.$element.find(".kv-alert-container .alert .close").off(t)},listen:function(){var t=this,i=t.$element.find(".kv-alert-container"),a=n.click,l=n.afterValidate,o=t.$element.find(".kv-detail-view");o.closest("form").off(l).on(l,function(e,t){void 0!==t&&o.removeClass("kv-detail-loading")}),t.$btnSave.off(a).on(a,function(){i.hide(),o.removeClass("kv-detail-loading").addClass("kv-detail-loading")}),t.$btnUpdate.off(a).on(a,function(){t.setMode("edit")}),t.$btnView.off(a).on(a,function(){t.setMode("view")}),t.$btnDelete.off(a).on(a,function(n){var a=e(this),l=t.deleteParams,d=t.deleteConfirm,s=t.deleteAjaxSettings||{};n.preventDefault(),(!d||confirm(d))&&(s=e.extend({type:"post",dataType:"json",data:l,url:a.attr("href"),beforeSend:function(){i.html("").hide(),o.removeClass("kv-detail-loading").addClass("kv-detail-loading")},success:function(n){n.success&&(o.hide(),t.$btnDelete.attr("disabled","disabled"),t.$btnUpdate.attr("disabled","disabled"),t.$btnView.attr("disabled","disabled"),t.$btnSave.attr("disabled","disabled")),e.each(n.messages,function(e,n){i.append(t.alert(e,n))}),i.hide().fadeIn("slow",function(){o.removeClass("kv-detail-loading"),t.initAlert()})},error:function(n,a,l){var d="";t.showErrorStack&&(d=n.responseText?e(n.responseText).text():"",d=d&&d.length?"
"+e.trim(d).replace(/\n\s*\n/g,"\n").replace(/":""),d=t.alert("kv-detail-error",l+d),o.removeClass("kv-detail-loading"),i.html(d).hide().fadeIn("slow"),t.initAlert()}},s),e.ajax(s))}),t.initAlert()},setMode:function(e){var t=this,n=t.fadeDelay;"edit"===e?(t.$attribs.fadeOut(n,function(){t.$formAttribs.fadeIn(n),t.$element.removeClass("kv-view-mode kv-edit-mode").addClass("kv-edit-mode")}),t.$buttons1.fadeOut(n,function(){t.$buttons2.fadeIn(n)})):(t.$formAttribs.fadeOut(n,function(){t.$attribs.fadeIn(n),t.$element.removeClass("kv-view-mode kv-edit-mode").addClass("kv-view-mode")}),t.$buttons2.fadeOut(n,function(){t.$buttons1.fadeIn(n)}))},initElements:function(){var e=this,t=e.$element;e.$btnUpdate=t.find(".kv-btn-update"),e.$btnDelete=t.find(".kv-btn-delete"),e.$btnView=t.find(".kv-btn-view"),e.$btnSave=t.find(".kv-btn-save"),e.$attribs=t.find(".kv-attribute"),e.$formAttribs=t.find(".kv-form-attribute"),e.$buttons1=t.find(".kv-buttons-1"),e.$buttons2=t.find(".kv-buttons-2")}},e.fn.kvDetailView=function(t){var n=Array.apply(null,arguments);return n.shift(),this.each(function(){var a=e(this),l=a.data("kvDetailView"),o="object"==typeof t&&t;l||(l=new i(this,e.extend({},e.fn.kvDetailView.defaults,o,e(this).data())),a.data("kvDetailView",l)),"string"==typeof t&&l[t].apply(l,n)})},e.fn.kvDetailView.defaults={mode:"view",fadeDelay:800,alertTemplate:"",alertMessageSettings:{},deleteParams:{},deleteAjaxSettings:{},deleteConfirm:"",showErrorStack:!1},e.fn.kvDetailView.Constructor=i}(window.jQuery);
\ No newline at end of file
diff --git a/composer.json b/composer.json
index af07fd5..9d67254 100644
--- a/composer.json
+++ b/composer.json
@@ -1,7 +1,15 @@
 {
     "name": "kartik-v/yii2-detail-view",
     "description": "Various enhancements to the Yii 2 Detail View with multi models, ability to edit data, manage Bootstrap 3 styles and more.",
-    "keywords": ["yii2", "extension", "widget", "detail", "grid", "form", "detail view"],
+    "keywords": [
+        "yii2",
+        "extension",
+        "widget",
+        "detail",
+        "grid",
+        "form",
+        "detail view"
+    ],
     "homepage": "https://github.com/kartik-v/yii2-detail-view",
     "type": "yii2-extension",
     "license": "BSD-3-Clause",