├── .github ├── FUNDING.yml ├── PULL_REQUEST_TEMPLATE.md ├── stale.yml ├── ISSUE_TEMPLATE.md └── CONTRIBUTING.md ├── src ├── Bs4CustomFileInputAsset.php ├── ActiveFormAsset.php ├── assets │ ├── js │ │ ├── bs-custom-file-input.min.js │ │ ├── activeform.min.js │ │ ├── bs-custom-file-input.js │ │ └── activeform.js │ └── css │ │ ├── activeform.min.css │ │ └── activeform.css ├── ActiveForm.php └── ActiveField.php ├── composer.json ├── LICENSE.md ├── README.md └── CHANGE.md /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | # github: [kartik-v] 4 | open_collective: yii2-widgets 5 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Scope 2 | This pull request includes a 3 | 4 | - [ ] Bug fix 5 | - [ ] New feature 6 | - [ ] Translation 7 | 8 | ## Changes 9 | The following changes were made (this change is also documented in the [change log](https://github.com/kartik-v/yii2-widget-activeform/blob/master/CHANGE.md)): 10 | 11 | - 12 | - 13 | - 14 | 15 | ## Related Issues 16 | If this is related to an existing ticket, include a link to it as well. -------------------------------------------------------------------------------- /.github/stale.yml: -------------------------------------------------------------------------------- 1 | # Number of days of inactivity before an issue becomes stale 2 | daysUntilStale: 60 3 | # Number of days of inactivity before a stale issue is closed 4 | daysUntilClose: 7 5 | # Issues with these labels will never be considered stale 6 | exemptLabels: 7 | - bug 8 | - enhancement 9 | - pinned 10 | - security 11 | # Label to use when marking an issue as stale 12 | staleLabel: wontfix 13 | # Comment to post when marking an issue as stale. Set to `false` to disable 14 | markComment: > 15 | This issue has been automatically marked as stale because it has not had 16 | recent activity. It will be closed if no further activity occurs. Thank you 17 | for your contributions. 18 | # Comment to post when closing a stale issue. Set to `false` to disable 19 | closeComment: false -------------------------------------------------------------------------------- /src/Bs4CustomFileInputAsset.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 1.0 19 | */ 20 | class Bs4CustomFileInputAsset extends PluginAssetBundle 21 | { 22 | /** 23 | * @inheritdoc 24 | */ 25 | public function init() 26 | { 27 | $this->setSourcePath(__DIR__ . '/assets'); 28 | $this->setupAssets('js', ['js/bs-custom-file-input']); 29 | parent::init(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/ActiveFormAsset.php: -------------------------------------------------------------------------------- 1 | 18 | * @since 1.0 19 | */ 20 | class ActiveFormAsset extends PluginAssetBundle 21 | { 22 | /** 23 | * @inheritdoc 24 | */ 25 | public function init() 26 | { 27 | $this->depends = array_merge($this->depends, [ 28 | 'yii\widgets\ActiveFormAsset', 29 | ]); 30 | $this->setSourcePath(__DIR__ . '/assets'); 31 | $this->setupAssets('css', ['css/activeform']); 32 | $this->setupAssets('js', ['js/activeform']); 33 | parent::init(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kartik-v/yii2-widget-activeform", 3 | "description": "Enhanced Yii2 active-form and active-field with full bootstrap styling support (sub repo split from yii2-widgets).", 4 | "keywords": [ 5 | "yii2", 6 | "extension", 7 | "widget", 8 | "activeform", 9 | "activefield", 10 | "form", 11 | "field" 12 | ], 13 | "homepage": "https://github.com/kartik-v/yii2-widget-activeform", 14 | "type": "yii2-extension", 15 | "license": "BSD-3-Clause", 16 | "authors": [ 17 | { 18 | "name": "Kartik Visweswaran", 19 | "email": "kartikv2@gmail.com", 20 | "homepage": "http://www.krajee.com/" 21 | } 22 | ], 23 | "require": { 24 | "kartik-v/yii2-krajee-base": ">=3.0.3" 25 | }, 26 | "autoload": { 27 | "psr-4": { 28 | "kartik\\form\\": "src" 29 | } 30 | }, 31 | "extra": { 32 | "branch-alias": { 33 | "dev-master": "1.6.x-dev" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 - 2023, Kartik Visweswaran 2 | Krajee.com 3 | All rights reserved. 4 | 5 | Redistribution and use in source and binary forms, with or without modification, 6 | are permitted provided that the following conditions are met: 7 | 8 | * Redistributions of source code must retain the above copyright notice, this 9 | list of conditions and the following disclaimer. 10 | 11 | * Redistributions in binary form must reproduce the above copyright notice, this 12 | list of conditions and the following disclaimer in the documentation and/or 13 | other materials provided with the distribution. 14 | 15 | * Neither the names of Kartik Visweswaran or Krajee nor the names of its 16 | contributors may be used to endorse or promote products derived from 17 | this software without specific prior written permission. 18 | 19 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 20 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 21 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 22 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR 23 | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 24 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 25 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 26 | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 28 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Prerequisites 2 | 3 | - [ ] I have searched for similar issues in both open and closed tickets and cannot find a duplicate. 4 | - [ ] The issue still exists against the latest `master` branch of yii2-widget-activeform. 5 | - [ ] This is not an usage question. I confirm having gone through and read the documentation for [ActiveForm](http://demos.krajee.com/widget-details/active-form) and [ActiveField](http://demos.krajee.com/widget-details/active-field). 6 | - [ ] This is not a general programming / coding question. (Those should be directed to the [webtips Q & A forum](http://webtips.krajee.com/questions)). 7 | - [ ] I have attempted to find the simplest possible steps to reproduce the issue. 8 | - [ ] I have included a failing test as a pull request (Optional). 9 | 10 | ## Steps to reproduce the issue 11 | 12 | 1. 13 | 2. 14 | 3. 15 | 16 | ## Expected behavior and actual behavior 17 | 18 | When I follow those steps, I see... 19 | 20 | I was expecting... 21 | 22 | ## Environment 23 | 24 | #### Browsers 25 | 26 | - [ ] Google Chrome 27 | - [ ] Mozilla Firefox 28 | - [ ] Internet Explorer 29 | - [ ] Safari 30 | 31 | #### Operating System 32 | 33 | - [ ] Windows 34 | - [ ] Mac OS X 35 | - [ ] Linux 36 | - [ ] Mobile 37 | 38 | #### Libraries 39 | 40 | - jQuery version: 41 | - yii2-widget-activeform version: 42 | 43 | ## Isolating the problem 44 | 45 | - [ ] This bug happens [on the demos page](http://demos.krajee.com/widget-details/active-form) 46 | - [ ] The bug happens consistently across all tested browsers 47 | - [ ] This bug happens when using yii2-widget-activeform without other plugins. -------------------------------------------------------------------------------- /src/assets/js/bs-custom-file-input.min.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bsCustomFileInput v1.3.2 (https://github.com/Johann-S/bs-custom-file-input) 3 | * Copyright 2018 - 2023 Johann-S 4 | * Licensed under MIT (https://github.com/Johann-S/bs-custom-file-input/blob/master/LICENSE) 5 | */ 6 | !function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e=e||self).bsCustomFileInput=t()}(this,function(){"use strict";var d={CUSTOMFILE:'.custom-file input[type="file"]',CUSTOMFILELABEL:".custom-file-label",FORM:"form",INPUT:"input"},r=function(e){if(0 4 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2015 - 2023 5 | * @version 1.6.4 6 | * 7 | * Active Field Hints Display Module 8 | * 9 | * Author: Kartik Visweswaran 10 | * Copyright: 2015, Kartik Visweswaran, Krajee.com 11 | * For more JQuery plugins visit http://plugins.krajee.com 12 | * For more Yii related demos visit http://demos.krajee.com 13 | */ 14 | var kvBs4InitForm=function(){};!function(t){"use strict";var e,i;e={NAMESPACE:".kvActiveField",isEmpty:function(e,i){return void 0===e||e===[]||null===e||""===e||i&&""===t.trim(e)}},(i=function(e,i){var n=this;n.$element=t(e),t.each(i,function(t,e){n[t]=e}),n.init()}).prototype={constructor:i,init:function(){var i,n=this,s=n.$element,a=s.find(".kv-hint-block"),o=a.html(),l=s.find(".kv-hintable");a.hide(),e.isEmpty(o)||(e.isEmpty(n.contentCssClass)||(i=t(document.createElement("span")).addClass(n.contentCssClass).append(o),i=t(document.createElement("span")).append(i),o=i.html(),i.remove()),l.each(function(){var e=t(this);e.hasClass("kv-type-label")?e.removeClass(n.labelCssClass).addClass(n.labelCssClass):e.removeClass("hide "+n.iconCssClass).addClass(n.iconCssClass),e.hasClass("kv-hint-click")&&n.listen("click",e,o),e.hasClass("kv-hint-hover")&&n.listen("hover",e,o)}),n.hideOnEscape&&t(document).on("keyup",function(e){l.each(function(){var i=t(this);27===e.which&&i.popover("hide")})}),n.hideOnClickOut&&t("body").on("click",function(e){l.each(function(){var i=t(this);i.is(e.target)||0!==i.has(e.target).length||0!==t(".popover").has(e.target).length||i.popover("hide")})}))},listen:function(t,i,n){var s={html:!0,trigger:"manual",content:n,title:this.title,placement:this.placement,container:this.container||!1,animation:!!this.animation,delay:this.delay,selector:this.selector};e.isEmpty(this.template)||(s.template=this.template),e.isEmpty(this.viewport)||(s.viewport=this.viewport),i.popover(s),"click"!==t?(this.raise(i,"mouseenter",function(){i.popover("show")}),this.raise(i,"mouseleave",function(){i.popover("hide")})):this.raise(i,"click",function(t){t.preventDefault(),i.popover("toggle")})},raise:function(t,i,n){i+=e.NAMESPACE,t.off(i).on(i,n)}},t.fn.activeFieldHint=function(e){var n=Array.apply(null,arguments);return n.shift(),this.each(function(){var s=t(this),a=s.data("activeFieldHint"),o="object"==typeof e&&e;a||s.data("activeFieldHint",a=new i(this,t.extend({},t.fn.activeFieldHint.defaults,o,t(this).data()))),"string"==typeof e&&a[e].apply(a,n)})},t.fn.activeFieldHint.defaults={labelCssClass:"kv-hint-label",iconCssClass:"kv-hint-icon",contentCssClass:"kv-hint-content",hideOnEscape:!1,hideOnClickOut:!1,title:"",placement:"right",container:"body",delay:0,animation:!0,selector:!1,template:"",viewport:""},kvBs4InitForm=function(){var e=[".form-control",".custom-control-input",".custom-select",".custom-range",".custom-file-input"],i=e.join(","),n=".has-error "+e.join(",.has-error "),s=".has-success "+e.join(",.has-success "),a=function(t){t.find(i).removeClass("is-valid is-invalid")};t("form").on("afterValidateAttribute",function(){var e=t(this);a(e),(e.find(".has-error").length||e.find(".has-success").length)&&(e.find(n).addClass("is-invalid"),e.find(s).addClass("is-valid"))}).on("reset",function(){var e=t(this);setTimeout(function(){a(e)},100)})}}(window.jQuery); -------------------------------------------------------------------------------- /src/assets/css/activeform.min.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2015 - 2023 3 | * @package yii2-widgets 4 | * @subpackage yii2-widget-activeform 5 | * @version 1.6.4 6 | * 7 | * Active Form Styling for Bootstrap 3.x & Bootstrap 4.x 8 | * Built for Yii Framework 2.0 9 | * For more Yii related demos visit http://demos.krajee.com 10 | */.kv-hint-icon{display:inline-block;float:right;font-size:16px;margin-left:4px;vertical-align:middle;cursor:help;opacity:.8}.kv-hint-icon:focus,.kv-hint-icon:hover{opacity:1}.kv-hint-content{opacity:.9}.kv-hint-label{border-bottom:1px dashed #888;cursor:help}.disabled label,.readonly label,label.disabled,label.readonly{cursor:not-allowed;-webkit-box-shadow:none;box-shadow:none;opacity:.65}.input-multiselect{overflow:auto;min-height:145px!important}.kv-form-bs4.tooltip-feedback .form-group.has-error{position:relative;margin-bottom:2rem}.kv-form-bs4 .has-feedback{position:relative}.kv-form-bs4 .has-feedback .form-control{padding-right:2.65625rem}.kv-form-bs4 .form-control-feedback{position:absolute;top:0;right:0;z-index:2;width:2.125rem;height:2.125rem;line-height:2.125rem;text-align:center;pointer-events:none}.has-feedback.has-size-lg .form-control-feedback{font-size:18px;top:31px}.has-feedback.has-size-sm .form-control-feedback{font-size:12px}.kv-form-bs4 .has-feedback.has-size-lg .form-control-feedback{width:2.875rem;height:2.875rem;line-height:2.875rem;font-size:1.25rem}.kv-form-bs4 .has-feedback.has-size-sm .form-control-feedback{width:1.75rem;height:1.75rem;line-height:1.75rem;font-size:.875rem}.has-error .form-control-feedback{color:#a94442}.kv-form-bs4 .has-feedback label~.form-control-feedback{top:2.125rem}.kv-form-bs4 .has-feedback label.sr-only~.form-control-feedback{top:0}.has-error .kv-feedback-default,.has-success .kv-feedback-default,.kv-feedback-error,.kv-feedback-success{display:none}.has-error .kv-feedback-error,.has-success .kv-feedback-success{display:inline-block}@media (min-width:768px){.form-inline .form-control,.form-inline .form-group{vertical-align:top}.form-inline .btn-default{margin:0}.form-inline .checkbox,.form-inline .radio{padding-top:6px}.kv-form-bs4.form-inline .has-feedback .form-control-feedback,.kv-form-bs4.navbar-form .has-feedback .form-control-feedback{top:0}}.kv-form-bs4.form-horizontal .has-feedback .form-control-feedback{right:.9375rem}.input-group .input-group-addon:not(:last-child){border-right:0}.input-group .input-group-addon:last-child{border-left:1px solid #ccc}.form-control+.input-group-addon:not(:first-child){border-left:0}.has-success.highlight-addon .input-group-addon{color:#3c763d;background-color:#dff0d8;border-color:#3c763d}.has-error.highlight-addon .input-group-addon{color:#a94442;background-color:#f2dede;border-color:#a94442}.has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled),.has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover{color:#fff;background-color:#449d44;border-color:#398439}.has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled),.has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover{color:#fff;background-color:#c9302c;border-color:#ac2925}.kv-form-bs4 .has-success.highlight-addon .input-group-text{color:#28a745;background-color:#d4edda;border-color:#28a745}.kv-form-bs4 .has-error.highlight-addon .input-group-text{color:#dc3545;background-color:#f8d7da;border-color:#dc3545}.kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled){color:#fff;background-color:#28a745;border-color:#28a745}.kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled):hover{color:#fff;background-color:#218838;border-color:#1e7e34}.kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled):focus{box-shadow:0 0 0 .2rem rgba(40,167,69,.5)}.kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled){color:#fff;background-color:#dc3545;border-color:#dc3545}.kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled):hover{color:#fff;background-color:#c82333;border-color:#bd2130}.kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled):focus{box-shadow:0 0 0 .2rem rgba(220,53,69,.5)}.kv-form-bs4 .has-error .invalid-feedback{display:block}.kv-form-bs4 .hint-block{font-size:.75rem;margin-top:.375rem;color:#999}.kv-form-bs3 .hint-block{font-size:12px;margin-top:-5px;color:#999}.kv-form-bs3 .help-block-error{font-size:90%}.checkbox.not-enclosed,.radio.not-enclosed{padding-left:20px}.checkbox.not-enclosed label,.radio.not-enclosed label{padding-left:2px}.form-inline .checkbox.not-enclosed,.form-inline .radio.not-enclosed{padding-left:5px;padding-right:5px}.form-inline .checkbox.not-enclosed label,.form-inline .radio.not-enclosed label{margin-top:-5px}.is-required::after,.required .has-star:not(.custom-control-label):not(.custom-file-label)::after{content:"*";margin-left:3px;font-weight:400;font-family:SFMono-Regular,Menlo,Monaco,Consolas,"Liberation Mono","Courier New",monospace;color:tomato}.form-floating>.form-control-plaintext~label.has-star::after,.form-floating>.form-control:focus~label.has-star::after,.form-floating>.form-control:not(:placeholder-shown)~label.has-star::after,.form-floating>.form-select~label.has-star::after{content:none;margin-left:none}.bs5-form-check.form-check-input,.bs5-form-check.form-check-input:active,.bs5-form-check.form-check-input:checked,.bs5-form-check.form-check-input:focus,.bs5-form-check.form-check-input:indeterminate{background:0 0;border:none;box-shadow:none;appearance:auto;-webkit-appearance:auto;-moz-appearance:auto} -------------------------------------------------------------------------------- /src/assets/js/bs-custom-file-input.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * bsCustomFileInput v1.3.2 (https://github.com/Johann-S/bs-custom-file-input) 3 | * Copyright 2018 - 2023 Johann-S 4 | * Licensed under MIT (https://github.com/Johann-S/bs-custom-file-input/blob/master/LICENSE) 5 | */ 6 | (function (global, factory) { 7 | typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : 8 | typeof define === 'function' && define.amd ? define(factory) : 9 | (global = global || self, global.bsCustomFileInput = factory()); 10 | }(this, function () { 'use strict'; 11 | 12 | var Selector = { 13 | CUSTOMFILE: '.custom-file input[type="file"]', 14 | CUSTOMFILELABEL: '.custom-file-label', 15 | FORM: 'form', 16 | INPUT: 'input' 17 | }; 18 | 19 | var textNodeType = 3; 20 | 21 | var getDefaultText = function getDefaultText(input) { 22 | var defaultText = ''; 23 | var label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL); 24 | 25 | if (label) { 26 | defaultText = label.innerHTML; 27 | } 28 | 29 | return defaultText; 30 | }; 31 | 32 | var findFirstChildNode = function findFirstChildNode(element) { 33 | if (element.childNodes.length > 0) { 34 | var childNodes = [].slice.call(element.childNodes); 35 | 36 | for (var i = 0; i < childNodes.length; i++) { 37 | var node = childNodes[i]; 38 | 39 | if (node.nodeType !== textNodeType) { 40 | return node; 41 | } 42 | } 43 | } 44 | 45 | return element; 46 | }; 47 | 48 | var restoreDefaultText = function restoreDefaultText(input) { 49 | var defaultText = input.bsCustomFileInput.defaultText; 50 | var label = input.parentNode.querySelector(Selector.CUSTOMFILELABEL); 51 | 52 | if (label) { 53 | var element = findFirstChildNode(label); 54 | element.innerHTML = defaultText; 55 | } 56 | }; 57 | 58 | var fileApi = !!window.File; 59 | var FAKE_PATH = 'fakepath'; 60 | var FAKE_PATH_SEPARATOR = '\\'; 61 | 62 | var getSelectedFiles = function getSelectedFiles(input) { 63 | if (input.hasAttribute('multiple') && fileApi) { 64 | return [].slice.call(input.files).map(function (file) { 65 | return file.name; 66 | }).join(', '); 67 | } 68 | 69 | if (input.value.indexOf(FAKE_PATH) !== -1) { 70 | var splittedValue = input.value.split(FAKE_PATH_SEPARATOR); 71 | return splittedValue[splittedValue.length - 1]; 72 | } 73 | 74 | return input.value; 75 | }; 76 | 77 | function handleInputChange() { 78 | var label = this.parentNode.querySelector(Selector.CUSTOMFILELABEL); 79 | 80 | if (label) { 81 | var element = findFirstChildNode(label); 82 | var inputValue = getSelectedFiles(this); 83 | 84 | if (inputValue.length) { 85 | element.innerHTML = inputValue; 86 | } else { 87 | restoreDefaultText(this); 88 | } 89 | } 90 | } 91 | 92 | function handleFormReset() { 93 | var customFileList = [].slice.call(this.querySelectorAll(Selector.INPUT)).filter(function (input) { 94 | return !!input.bsCustomFileInput; 95 | }); 96 | 97 | for (var i = 0, len = customFileList.length; i < len; i++) { 98 | restoreDefaultText(customFileList[i]); 99 | } 100 | } 101 | 102 | var customProperty = 'bsCustomFileInput'; 103 | var Event = { 104 | FORMRESET: 'reset', 105 | INPUTCHANGE: 'change' 106 | }; 107 | var bsCustomFileInput = { 108 | init: function init(inputSelector, formSelector) { 109 | if (inputSelector === void 0) { 110 | inputSelector = Selector.CUSTOMFILE; 111 | } 112 | 113 | if (formSelector === void 0) { 114 | formSelector = Selector.FORM; 115 | } 116 | 117 | var customFileInputList = [].slice.call(document.querySelectorAll(inputSelector)); 118 | var formList = [].slice.call(document.querySelectorAll(formSelector)); 119 | 120 | for (var i = 0, len = customFileInputList.length; i < len; i++) { 121 | var input = customFileInputList[i]; 122 | Object.defineProperty(input, customProperty, { 123 | value: { 124 | defaultText: getDefaultText(input) 125 | }, 126 | writable: true 127 | }); 128 | handleInputChange.call(input); 129 | input.addEventListener(Event.INPUTCHANGE, handleInputChange); 130 | } 131 | 132 | for (var _i = 0, _len = formList.length; _i < _len; _i++) { 133 | formList[_i].addEventListener(Event.FORMRESET, handleFormReset); 134 | 135 | Object.defineProperty(formList[_i], customProperty, { 136 | value: true, 137 | writable: true 138 | }); 139 | } 140 | }, 141 | destroy: function destroy() { 142 | var formList = [].slice.call(document.querySelectorAll(Selector.FORM)).filter(function (form) { 143 | return !!form.bsCustomFileInput; 144 | }); 145 | var customFileInputList = [].slice.call(document.querySelectorAll(Selector.INPUT)).filter(function (input) { 146 | return !!input.bsCustomFileInput; 147 | }); 148 | 149 | for (var i = 0, len = customFileInputList.length; i < len; i++) { 150 | var input = customFileInputList[i]; 151 | restoreDefaultText(input); 152 | input[customProperty] = undefined; 153 | input.removeEventListener(Event.INPUTCHANGE, handleInputChange); 154 | } 155 | 156 | for (var _i2 = 0, _len2 = formList.length; _i2 < _len2; _i2++) { 157 | formList[_i2].removeEventListener(Event.FORMRESET, handleFormReset); 158 | 159 | formList[_i2][customProperty] = undefined; 160 | } 161 | } 162 | }; 163 | 164 | return bsCustomFileInput; 165 | 166 | })); -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | Contributing to yii2-widget-activeform 2 | ====================================== 3 | Looking to contribute something to yii2-widget-activeform? **Here's how you can help.** 4 | 5 | Please take a moment to review this document in order to make the contribution 6 | process easy and effective for everyone involved. 7 | 8 | Following these guidelines helps to communicate that you respect the time of 9 | the developers managing and developing this open source project. In return, 10 | they should reciprocate that respect in addressing your issue or assessing 11 | patches and features. 12 | 13 | Using the issue tracker 14 | ----------------------- 15 | When [reporting bugs][reporting-bugs] or 16 | [requesting features][requesting-features], the 17 | [issue tracker on GitHub][issue-tracker] is the recommended channel to use. 18 | 19 | The issue tracker **is not** a place for support requests. Refer the 20 | [extension documentation and demos](http://demos.krajee.com/widget-details/active-form) and/or refer to the 21 | [webtips Q & A forum](http://webtips.krajee.com/questions) which are the better places to get help. 22 | 23 | Reporting bugs with yii2-widget-activeform 24 | ------------------------------------------ 25 | We really appreciate clear bug reports that _consistently_ show an issue 26 | _within yii2-widget-activeform_. 27 | 28 | The ideal bug report follows these guidelines: 29 | 30 | 1. **Use the [GitHub issue search][issue-search]** — Check if the issue 31 | has already been reported. 32 | 2. **Check if the issue has been fixed** — Try to reproduce the problem 33 | using the code in the `master` branch. 34 | 3. **Isolate the problem** — Try to share a demo or a test case that 35 | consistently reproduces the problem. 36 | 37 | Please try to be as detailed as possible in your bug report, especially if an 38 | isolated test case cannot be made. Some useful questions to include the answer 39 | to are: 40 | 41 | - What steps can be used to reproduce the issue? 42 | - What is the bug and what is the expected outcome? 43 | - What browser(s) and Operating System have you tested with? 44 | - Does the bug happen consistently across all tested browsers? 45 | - What version of jQuery are you using? And what version of yii2-widget-activeform? 46 | - Are you using yii2-widget-activeform with other plugins? 47 | 48 | All of these questions will help others fix and identify any potential bugs. 49 | 50 | Requesting features in yii2-widget-activeform 51 | ------------------------------------------ 52 | Before starting work on a major feature for yii2-widget-activeform, **read the 53 | [documentation](http://demos.krajee.com/widget-details/active-form) first** or you may risk spending a considerable amount of 54 | time on something which the project developers are not interested in bringing into the project. 55 | 56 | ### Submitting a pull request 57 | 58 | We use GitHub's pull request system for submitting patches. Here are some 59 | guidelines to follow when creating the pull request for your fix. 60 | 61 | 1. Make sure to create a ticket for your pull request. This will serve as the 62 | bug ticket, and any discussion about the bug will take place there. Your pull 63 | request will be focused on the specific changes that fix the bug. 64 | 2. Make sure to reference the ticket you are fixing within your pull request. 65 | This will allow us to close off the ticket once we merge the pull request, or 66 | follow up on the ticket if there are any related blocking issues. 67 | 3. Explain why the specific change was made. Not everyone who is reviewing your 68 | pull request will be familiar with the problem it is fixing. 69 | 4. Run your tests first. If your tests aren't passing, the pull request won't 70 | be able to be merged. If you're breaking existing tests, make sure that you 71 | aren't causing any breaking changes. 72 | 5. Only include source changes. While it's not required, only including changes 73 | from the `src` directory will prevent merge conflicts from occuring. Making 74 | this happen can be as a simple as not committing changes from the `dist` 75 | directory. 76 | 77 | By following these steps, you will make it easier for your pull request to be 78 | reviewed and eventually merged. 79 | 80 | Triaging issues and pull requests 81 | --------------------------------- 82 | Anyone can help the project maintainers triage issues and review pull requests. 83 | 84 | ### Handling new issues 85 | 86 | yii2-widget-activeform regularly receives new issues which need to be tested and organized. 87 | 88 | When a new issue that comes in that is similar to another existing issue, it 89 | should be checked to make sure it is not a duplicate. Duplicates issues should 90 | be marked by replying to the issue with "Duplicate of #[issue number]" where 91 | `[issue number]` is the url or issue number for the existing issue. This will 92 | allow the project maintainers to quickly close off additional issues and keep 93 | the discussion focused within a single issue. 94 | 95 | If you can test issues that are reported to yii2-widget-activeform that contain test cases and 96 | confirm under what conditions bugs happen, that will allow others to identify 97 | what causes a bug quicker. 98 | 99 | ### Reviewing pull requests 100 | 101 | It is very common for pull requests to be opened for issues that contain a clear 102 | solution to the problem. These pull requests should be rigorously reviewed by 103 | the community before being accepted. If you are not sure about a piece of 104 | submitted code, or know of a better way to do something, do not hesitate to make 105 | a comment on the pull request. 106 | 107 | ### Reviving old tickets 108 | 109 | If you come across tickets which have not been updated for a while, you are 110 | encouraged to revive them. While this can be as simple as saying `:+1:`, it is 111 | best if you can include more information on the issue. Common bugs and feature 112 | requests are more likely to be fixed, whether it is by the community or the 113 | developers, so keeping tickets up to date is encouraged. 114 | 115 | Licensing 116 | --------- 117 | 118 | It should also be made clear that **all code contributed to yii2-widget-activeform** must be 119 | licensable under the [BSD-3-Clause license][licensing]. Code that cannot be released 120 | under this license **cannot be accepted** into the project. 121 | 122 | [issue-search]: https://github.com/kartik-v/yii2-widget-activeform/search?q=&type=Issues 123 | [issue-tracker]: https://github.com/kartik-v/yii2-widget-activeform/issues 124 | [licensing]: https://github.com/kartik-v/yii2-widget-activeform/blob/master/LICENSE.md 125 | [reporting-bugs]: #reporting-bugs-with-yii2-widget-activeform 126 | [requesting-features]: #requesting-features-in-yii2-widget-activeform -------------------------------------------------------------------------------- /src/assets/js/activeform.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * @package yii2-widget-activeform 3 | * @author Kartik Visweswaran 4 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2015 - 2023 5 | * @version 1.6.4 6 | * 7 | * Active Field Hints Display Module 8 | * 9 | * Author: Kartik Visweswaran 10 | * Copyright: 2015, Kartik Visweswaran, Krajee.com 11 | * For more JQuery plugins visit http://plugins.krajee.com 12 | * For more Yii related demos visit http://demos.krajee.com 13 | */ 14 | var kvBs4InitForm = function () { 15 | }; 16 | (function ($) { 17 | "use strict"; 18 | var $h, ActiveFieldHint; 19 | $h = { 20 | NAMESPACE: '.kvActiveField', 21 | isEmpty: function (val, trim) { 22 | return val === undefined || val === [] || val === null || val === '' || trim && $.trim(val) === ''; 23 | } 24 | }; 25 | ActiveFieldHint = function (element, options) { 26 | var self = this; 27 | self.$element = $(element); 28 | $.each(options, function (key, val) { 29 | self[key] = val; 30 | }); 31 | self.init(); 32 | }; 33 | ActiveFieldHint.prototype = { 34 | constructor: ActiveFieldHint, 35 | init: function () { 36 | var self = this, $el = self.$element, $block = $el.find('.kv-hint-block'), content = $block.html(), 37 | $hints = $el.find('.kv-hintable'), $span; 38 | $block.hide(); 39 | if ($h.isEmpty(content)) { 40 | return; 41 | } 42 | if (!$h.isEmpty(self.contentCssClass)) { 43 | $span = $(document.createElement('span')).addClass(self.contentCssClass).append(content); 44 | $span = $(document.createElement('span')).append($span); 45 | content = $span.html(); 46 | $span.remove(); 47 | } 48 | $hints.each(function () { 49 | var $src = $(this); 50 | if ($src.hasClass('kv-type-label')) { 51 | $src.removeClass(self.labelCssClass).addClass(self.labelCssClass); 52 | } else { 53 | $src.removeClass('hide ' + self.iconCssClass).addClass(self.iconCssClass); 54 | } 55 | if ($src.hasClass('kv-hint-click')) { 56 | self.listen('click', $src, content); 57 | } 58 | if ($src.hasClass('kv-hint-hover')) { 59 | self.listen('hover', $src, content); 60 | } 61 | }); 62 | if (self.hideOnEscape) { 63 | $(document).on('keyup', function (e) { 64 | $hints.each(function () { 65 | var $src = $(this); 66 | if (e.which === 27) { 67 | $src.popover('hide'); 68 | } 69 | }); 70 | }); 71 | } 72 | if (self.hideOnClickOut) { 73 | $('body').on('click', function (e) { 74 | $hints.each(function () { 75 | var $src = $(this); 76 | if (!$src.is(e.target) && $src.has(e.target).length === 0 && $('.popover').has(e.target).length === 0) { 77 | $src.popover('hide'); 78 | } 79 | }); 80 | }); 81 | } 82 | }, 83 | listen: function (event, $src, content) { 84 | var self = this, opts = { 85 | html: true, 86 | trigger: 'manual', 87 | content: content, 88 | title: self.title, 89 | placement: self.placement, 90 | container: self.container || false, 91 | animation: !!self.animation, 92 | delay: self.delay, 93 | selector: self.selector 94 | }; 95 | if (!$h.isEmpty(self.template)) { 96 | opts.template = self.template; 97 | } 98 | if (!$h.isEmpty(self.viewport)) { 99 | opts.viewport = self.viewport; 100 | } 101 | $src.popover(opts); 102 | if (event === 'click') { 103 | self.raise($src, 'click', function (e) { 104 | e.preventDefault(); 105 | $src.popover('toggle'); 106 | }); 107 | return; 108 | } 109 | self.raise($src, 'mouseenter', function () { 110 | $src.popover('show'); 111 | }); 112 | self.raise($src, 'mouseleave', function () { 113 | $src.popover('hide'); 114 | }); 115 | }, 116 | raise: function ($elem, event, callback) { 117 | event = event + $h.NAMESPACE; 118 | $elem.off(event).on(event, callback); 119 | } 120 | }; 121 | 122 | //ActiveFieldHint plugin definition 123 | $.fn.activeFieldHint = function (option) { 124 | var args = Array.apply(null, arguments); 125 | args.shift(); 126 | return this.each(function () { 127 | var $this = $(this), 128 | data = $this.data('activeFieldHint'), 129 | options = typeof option === 'object' && option; 130 | 131 | if (!data) { 132 | $this.data('activeFieldHint', 133 | (data = new ActiveFieldHint(this, $.extend({}, $.fn.activeFieldHint.defaults, options, $(this).data())))); 134 | } 135 | 136 | if (typeof option === 'string') { 137 | data[option].apply(data, args); 138 | } 139 | }); 140 | }; 141 | 142 | $.fn.activeFieldHint.defaults = { 143 | labelCssClass: 'kv-hint-label', 144 | iconCssClass: 'kv-hint-icon', 145 | contentCssClass: 'kv-hint-content', 146 | hideOnEscape: false, 147 | hideOnClickOut: false, 148 | title: '', 149 | placement: 'right', 150 | container: 'body', 151 | delay: 0, 152 | animation: true, 153 | selector: false, 154 | template: '', 155 | viewport: '' 156 | }; 157 | 158 | kvBs4InitForm = function () { 159 | var controls = ['.form-control', '.custom-control-input', '.custom-select', '.custom-range', '.custom-file-input'], 160 | validControls = controls.join(','), 161 | errorControls = '.has-error ' + controls.join(',.has-error '), 162 | successControls = '.has-success ' + controls.join(',.has-success '), 163 | resetControls = function ($form) { 164 | $form.find(validControls).removeClass('is-valid is-invalid'); 165 | }; 166 | $('form').on('afterValidateAttribute', function () { 167 | var $form = $(this); 168 | resetControls($form); 169 | if ($form.find('.has-error').length || $form.find('.has-success').length) { 170 | $form.find(errorControls).addClass('is-invalid'); 171 | $form.find(successControls).addClass('is-valid'); 172 | } 173 | }).on('reset', function () { 174 | var $form = $(this); 175 | setTimeout(function () { 176 | resetControls($form); 177 | }, 100); 178 | }); 179 | }; 180 | })(window.jQuery); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | Krajee Logo 4 | 5 |
6 | yii2-widget-activeform 7 |
8 | Donate 10 |       11 | kartikv 12 |

13 | 14 | [![Stable Version](https://poser.pugx.org/kartik-v/yii2-widget-activeform/v/stable)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 15 | [![Unstable Version](https://poser.pugx.org/kartik-v/yii2-widget-activeform/v/unstable)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 16 | [![License](https://poser.pugx.org/kartik-v/yii2-widget-activeform/license)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 17 | [![Total Downloads](https://poser.pugx.org/kartik-v/yii2-widget-activeform/downloads)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 18 | [![Monthly Downloads](https://poser.pugx.org/kartik-v/yii2-widget-activeform/d/monthly)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 19 | [![Daily Downloads](https://poser.pugx.org/kartik-v/yii2-widget-activeform/d/daily)](https://packagist.org/packages/kartik-v/yii2-widget-activeform) 20 | 21 | Extends and enhances the [Yii ActiveForm widget](https://github.com/yiisoft/yii2/blob/master/framework/widgets/ActiveForm.php). Facilitates all [three form layouts](http://getbootstrap.com/css/#forms-example) available in Bootstrap i.e. __vertical__, __horizontal__, and __inline__. Allows options for offsetting labels and inputs for horizontal form layout. Works closely with the extended ActiveField component. In addition, this extension enhances and extends the [Yii ActiveField component](https://github.com/yiisoft/yii2/blob/master/framework/widgets/ActiveField.php). Allows Bootstrap styled [input group addons](http://getbootstrap.com/components/#input-groups-basic) to be prepended or appended to textInputs. Implements [feedback icons](http://getbootstrap.com/css/#with-optional-icons) within inputs based on contextual states. Automatically adjusts checkboxes and radio input offsets for horizontal forms. Allows, flexibility to control the labels and placeholders based on form layout style (e.g. hide labels and show them as placeholder for inline forms). The extended ActiveField functionalities available are: 22 | 23 | - Addons 24 | * Prepend Addon 25 | * Append Addon 26 | * Icon Addon 27 | * Input Addon 28 | * Button Addon 29 | * Button Dropdown Addon 30 | * Segmented Button Addon 31 | * Prepend & Append 32 | * Input Group Settings 33 | * Multiple Addons Configuration (_new_ since v1.4.9) 34 | - Input Feedback Icons 35 | - Input Hints Management 36 | - Inputs 37 | * Checkbox 38 | * Radio 39 | * Checkbox List 40 | * Radio List 41 | * Static Input 42 | * HTML 5 Input 43 | * Checkbox Button Group 44 | * Radio Button Group 45 | - Multi Select 46 | * Vertical Form 47 | * Horizontal Form 48 | * Radio List 49 | * Display Options 50 | 51 | > NOTE: This extension is a sub repo split of [yii2-widgets](https://github.com/kartik-v/yii2-widgets). The split has been done since 08-Nov-2014 to allow developers to install this specific widget in isolation if needed. One can also use the extension the previous way with the whole suite of [yii2-widgets](http://demos.krajee.com/widgets). 52 | 53 | ## Installation 54 | 55 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). Check the [composer.json](https://github.com/kartik-v/yii2-widget-activeform/blob/master/composer.json) for this extension's requirements and dependencies. Read this [web tip /wiki](http://webtips.krajee.com/setting-composer-minimum-stability-application/) on setting the `minimum-stability` settings for your application's composer.json. 56 | 57 | To install, either run 58 | 59 | ``` 60 | $ php composer.phar require kartik-v/yii2-widget-activeform "@dev" 61 | ``` 62 | 63 | or add 64 | 65 | ``` 66 | "kartik-v/yii2-widget-activeform": "@dev" 67 | ``` 68 | 69 | to the ```require``` section of your `composer.json` file. 70 | 71 | ## Release Changes 72 | 73 | > NOTE: Refer the [CHANGE LOG](https://github.com/kartik-v/yii2-widget-activeform/blob/master/CHANGE.md) for details on changes to various releases. 74 | 75 | ## Demo 76 | 77 | You can refer detailed documentation and demos for understanding the usage of the extension at these links below: 78 | 79 | - [ActiveForm](http://demos.krajee.com/widget-details/active-form) 80 | - [ActiveField](http://demos.krajee.com/widget-details/active-field) 81 | - [Html5Input](http://demos.krajee.com/html5-demo) 82 | 83 | ## Usage 84 | 85 | ### ActiveForm 86 | 87 | ```php 88 | // add this in your view 89 | use kartik\form\ActiveForm; 90 | 91 | // Vertical Form 92 | $form = ActiveForm::begin([ 93 | 'id' => 'form-signup', 94 | 'type' => ActiveForm::TYPE_VERTICAL 95 | ]); 96 | 97 | // Inline Form 98 | $form = ActiveForm::begin([ 99 | 'id' => 'form-login', 100 | 'type' => ActiveForm::TYPE_INLINE, 101 | 'fieldConfig' => ['autoPlaceholder'=>true] 102 | ]); 103 | 104 | // Horizontal Form Configuration 105 | $form = ActiveForm::begin([ 106 | 'id' => 'form-signup', 107 | 'type' => ActiveForm::TYPE_HORIZONTAL, 108 | 'formConfig' => ['labelSpan' => 3, 'deviceSize' => ActiveForm::SIZE_SMALL] 109 | ]); 110 | ``` 111 | 112 | ### ActiveField 113 | ```php 114 | // Implement a feedback icon 115 | echo $form->field($model, 'email_2', [ 116 | 'feedbackIcon' => [ 117 | 'default' => 'envelope', 118 | 'success' => 'ok', 119 | 'error' => 'exclamation-sign', 120 | 'defaultOptions' => ['class'=>'text-primary'] 121 | ] 122 | ])->textInput(['placeholder'=>'Enter a valid email address...']); 123 | 124 | // Prepend an addon text 125 | echo $form->field($model, 'email', ['addon' => ['prepend' => ['content'=>'@']]]); 126 | 127 | // Append an addon text 128 | echo $form->field($model, 'amount_paid', [ 129 | 'addon' => ['append' => ['content'=>'.00']] 130 | ]); 131 | 132 | // Formatted addons (like icons) 133 | echo $form->field($model, 'phone', [ 134 | 'addon' => [ 135 | 'prepend' => [ 136 | 'content' => '' 137 | ] 138 | ] 139 | ]); 140 | 141 | // Formatted addons (inputs) 142 | echo $form->field($model, 'phone', [ 143 | 'addon' => [ 144 | 'prepend' => [ 145 | 'content' => '' 146 | ] 147 | ] 148 | ]); 149 | 150 | // Formatted addons (buttons) 151 | echo $form->field($model, 'phone', [ 152 | 'addon' => [ 153 | 'prepend' => [ 154 | 'content' => Html::button('Go', ['class'=>'btn btn-primary']), 155 | 'asButton' => true 156 | ] 157 | ] 158 | ]); 159 | ``` 160 | 161 | ## License 162 | 163 | **yii2-widget-activeform** is released under the BSD-3-Clause License. See the bundled `LICENSE.md` for details. -------------------------------------------------------------------------------- /src/assets/css/activeform.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * @copyright Copyright © Kartik Visweswaran, Krajee.com, 2015 - 2023 3 | * @package yii2-widgets 4 | * @subpackage yii2-widget-activeform 5 | * @version 1.6.4 6 | * 7 | * Active Form Styling for Bootstrap 3.x & Bootstrap 4.x 8 | * Built for Yii Framework 2.0 9 | * For more Yii related demos visit http://demos.krajee.com 10 | */ 11 | .kv-hint-icon { 12 | display: inline-block; 13 | float: right; 14 | font-size: 16px; 15 | margin-left: 4px; 16 | vertical-align: middle; 17 | cursor: help; 18 | opacity: 0.8; 19 | } 20 | 21 | .kv-hint-icon:hover, .kv-hint-icon:focus { 22 | opacity: 1; 23 | } 24 | 25 | .kv-hint-content { 26 | opacity: 0.9; 27 | } 28 | 29 | .kv-hint-label { 30 | border-bottom: 1px dashed #888; 31 | cursor: help; 32 | } 33 | 34 | label.disabled, label.readonly, .disabled label, .readonly label { 35 | cursor: not-allowed; 36 | filter: alpha(opacity=65); 37 | -webkit-box-shadow: none; 38 | box-shadow: none; 39 | opacity: .65; 40 | } 41 | 42 | @media (min-width: 768px) { 43 | .form-inline .form-group, .form-inline .form-control { 44 | vertical-align: top; 45 | } 46 | 47 | .form-inline .btn-default { 48 | margin: 0; 49 | } 50 | 51 | .form-inline .checkbox, .form-inline .radio { 52 | padding-top: 6px; 53 | } 54 | } 55 | 56 | .input-multiselect { 57 | overflow: auto; 58 | min-height: 145px !important; 59 | } 60 | 61 | .kv-form-bs4.tooltip-feedback .form-group.has-error { 62 | position: relative; 63 | margin-bottom: 2rem; 64 | } 65 | 66 | /** 67 | * feedback icon styling 68 | */ 69 | .kv-form-bs4 .has-feedback { 70 | position: relative; 71 | } 72 | 73 | .kv-form-bs4 .has-feedback .form-control { 74 | padding-right: 2.65625rem; 75 | } 76 | 77 | .kv-form-bs4 .form-control-feedback { 78 | position: absolute; 79 | top: 0; 80 | right: 0; 81 | z-index: 2; 82 | width: 2.125rem; 83 | height: 2.125rem; 84 | line-height: 2.125rem; 85 | text-align: center; 86 | pointer-events: none; 87 | } 88 | 89 | .has-feedback.has-size-lg .form-control-feedback { 90 | font-size: 18px; 91 | top: 31px; 92 | } 93 | 94 | .has-feedback.has-size-sm .form-control-feedback { 95 | font-size: 12px; 96 | } 97 | 98 | .kv-form-bs4 .has-feedback.has-size-lg .form-control-feedback { 99 | width: 2.875rem; 100 | height: 2.875rem; 101 | line-height: 2.875rem; 102 | font-size: 1.25rem; 103 | } 104 | 105 | .kv-form-bs4 .has-feedback.has-size-sm .form-control-feedback { 106 | width: 1.75rem; 107 | height: 1.75rem; 108 | line-height: 1.75rem; 109 | font-size: 0.875rem; 110 | } 111 | 112 | .has-error .form-control-feedback { 113 | color: #a94442; 114 | } 115 | 116 | .kv-form-bs4 .has-feedback label ~ .form-control-feedback { 117 | top: 2.125rem; 118 | } 119 | 120 | .kv-form-bs4 .has-feedback label.sr-only ~ .form-control-feedback { 121 | top: 0; 122 | } 123 | 124 | .kv-feedback-success, 125 | .kv-feedback-error, 126 | .has-error .kv-feedback-default, 127 | .has-success .kv-feedback-default { 128 | display: none; 129 | } 130 | 131 | .has-error .kv-feedback-error, 132 | .has-success .kv-feedback-success { 133 | display: inline-block; 134 | } 135 | 136 | @media (min-width: 768px) { 137 | .kv-form-bs4.form-inline .has-feedback .form-control-feedback, 138 | .kv-form-bs4.navbar-form .has-feedback .form-control-feedback { 139 | top: 0; 140 | } 141 | } 142 | 143 | .kv-form-bs4.form-horizontal .has-feedback .form-control-feedback { 144 | right: 0.9375rem; 145 | } 146 | 147 | /** 148 | * multiple addons border fix 149 | **/ 150 | .input-group .input-group-addon:not(:last-child) { 151 | border-right: 0; 152 | } 153 | 154 | .input-group .input-group-addon:last-child { 155 | border-left: 1px solid #ccc; 156 | } 157 | 158 | .form-control + .input-group-addon:not(:first-child) { 159 | border-left: 0; 160 | } 161 | 162 | /** 163 | * addons success and error states 164 | **/ 165 | .has-success.highlight-addon .input-group-addon { 166 | color: #3c763d; 167 | background-color: #dff0d8; 168 | border-color: #3c763d; 169 | } 170 | 171 | .has-error.highlight-addon .input-group-addon { 172 | color: #a94442; 173 | background-color: #f2dede; 174 | border-color: #a94442; 175 | } 176 | 177 | .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled) { 178 | color: #fff; 179 | background-color: #449d44; 180 | border-color: #398439; 181 | } 182 | 183 | .has-success.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover { 184 | color: #fff; 185 | background-color: #449d44; 186 | border-color: #398439; 187 | } 188 | 189 | .has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled) { 190 | color: #fff; 191 | background-color: #c9302c; 192 | border-color: #ac2925; 193 | } 194 | 195 | .has-error.highlight-addon .input-group-btn .btn:not(:disabled):not(.disabled):hover { 196 | color: #fff; 197 | background-color: #c9302c; 198 | border-color: #ac2925; 199 | } 200 | 201 | .kv-form-bs4 .has-success.highlight-addon .input-group-text { 202 | color: #28a745; 203 | background-color: #d4edda; 204 | border-color: #28a745; 205 | } 206 | 207 | .kv-form-bs4 .has-error.highlight-addon .input-group-text { 208 | color: #dc3545; 209 | background-color: #f8d7da; 210 | border-color: #dc3545; 211 | } 212 | 213 | .kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled) { 214 | color: #fff; 215 | background-color: #28a745; 216 | border-color: #28a745; 217 | } 218 | 219 | .kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled):hover { 220 | color: #fff; 221 | background-color: #218838; 222 | border-color: #1e7e34; 223 | } 224 | 225 | .kv-form-bs4 .has-success.highlight-addon .input-group .btn:not(:disabled):not(.disabled):focus { 226 | box-shadow: 0 0 0 0.2rem rgba(40, 167, 69, 0.5); 227 | } 228 | 229 | .kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled) { 230 | color: #fff; 231 | background-color: #dc3545; 232 | border-color: #dc3545; 233 | } 234 | 235 | .kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled):hover { 236 | color: #fff; 237 | background-color: #c82333; 238 | border-color: #bd2130; 239 | } 240 | 241 | .kv-form-bs4 .has-error.highlight-addon .input-group .btn:not(:disabled):not(.disabled):focus { 242 | box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.5); 243 | } 244 | 245 | .kv-form-bs4 .has-error .invalid-feedback { 246 | display: block; 247 | } 248 | 249 | .kv-form-bs4 .hint-block { 250 | font-size: 0.75rem; 251 | margin-top: 0.375rem; 252 | color: #999; 253 | } 254 | 255 | .kv-form-bs3 .hint-block { 256 | font-size: 12px; 257 | margin-top: -5px; 258 | color: #999; 259 | } 260 | 261 | .kv-form-bs3 .help-block-error { 262 | font-size: 90%; 263 | } 264 | 265 | .checkbox.not-enclosed, 266 | .radio.not-enclosed { 267 | padding-left: 20px; 268 | } 269 | 270 | .checkbox.not-enclosed label, 271 | .radio.not-enclosed label { 272 | padding-left: 2px; 273 | } 274 | 275 | .form-inline .checkbox.not-enclosed, 276 | .form-inline .radio.not-enclosed { 277 | padding-left: 5px; 278 | padding-right: 5px; 279 | } 280 | 281 | .form-inline .checkbox.not-enclosed label, 282 | .form-inline .radio.not-enclosed label { 283 | margin-top: -5px; 284 | } 285 | 286 | .required .has-star:not(.custom-control-label):not(.custom-file-label)::after, 287 | .is-required::after { 288 | content: "*"; 289 | margin-left: 3px; 290 | font-weight: normal; 291 | font-family: SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; 292 | color: tomato; 293 | } 294 | 295 | .form-floating > .form-control:focus ~ label.has-star::after, 296 | .form-floating > .form-control:not(:placeholder-shown) ~ label.has-star::after, 297 | .form-floating > .form-control-plaintext ~ label.has-star::after, 298 | .form-floating > .form-select ~ label.has-star::after { 299 | content: none; 300 | margin-left: none; 301 | } 302 | 303 | .bs5-form-check.form-check-input, 304 | .bs5-form-check.form-check-input:checked, 305 | .bs5-form-check.form-check-input:focus, 306 | .bs5-form-check.form-check-input:active, 307 | .bs5-form-check.form-check-input:indeterminate { 308 | background: none; 309 | border: none; 310 | box-shadow: none; 311 | appearance: auto; 312 | -webkit-appearance: auto; 313 | -moz-appearance: auto; 314 | } -------------------------------------------------------------------------------- /CHANGE.md: -------------------------------------------------------------------------------- 1 | Change Log: `yii2-widget-activeform` 2 | ==================================== 3 | 4 | ## Version 1.6.4 5 | 6 | **Date**: 31-Jul-2023 7 | 8 | - (enh #139): Correct `parseFormFlag` method for `readonly` and `disabled` check. 9 | 10 | ## Version 1.6.3 11 | 12 | **Date**: 28-Jul-2023 13 | 14 | - (enh #138): Enhance required star for bootstrap 5 floating labels. 15 | - (enh #137): Overflowing labels in CheckboxList don't break correctly with Bootstrap 4. 16 | - (enh #136): Enhance ActiveForm to dynamically configure `enabled`, `readonly` and `staticOnly` flags. 17 | 18 | ## Version 1.6.2 19 | 20 | **Date**: 27-Feb-2022 21 | 22 | - PHP 8.1 enhancements for native functions. 23 | - (enh #135): Backward compatibility for PHP 5.6. 24 | 25 | ## Version 1.6.1 26 | 27 | **Date**: 13-Feb-2022 28 | 29 | - (enh #134): Correct form label required star styling for BS 5.x. 30 | - (enh #131): Enhance hint block styling. 31 | - (enh #130): Support isset validation for older PHP versions. 32 | - (enh #129): Enhance radioList and checkboxList styles. 33 | 34 | ## Version 1.6.0 35 | 36 | **Date**: 01-Sep-2021 37 | 38 | - (enh #128): Enhancements to support Bootstrap v5.x. 39 | 40 | ## Version 1.5.9 41 | 42 | **Date**: 01-Sep-2021 43 | 44 | - (enh #121): Fixed type hinting error. 45 | - (enh #120): BS4 Custom Controls Enhancements: Switch and File. 46 | 47 | ## Version 1.5.8 48 | 49 | **Date**: 24-Feb-2019 50 | 51 | - (enh #119): Correct required star styling for BS custom checkbox control label. 52 | - (enh #118): Add feature to show star indicator for required field labels. 53 | - (enh #117): Add ability to insert content at begin and end of rendered ActiveField. 54 | 55 | ## Version 1.5.7 56 | 57 | **Date**: 27-Sep-2018 58 | 59 | - (enh #115): Enhance rendering of Bootstrap 4.x custom file control. 60 | 61 | ## Version 1.5.6 62 | 63 | **Date**: 27-Sep-2018 64 | 65 | - Bump up version. 66 | 67 | ## Version 1.5.5 68 | 69 | **Date**: 26-Sep-2018 70 | 71 | - New `ActiveForm` methods `isHorizontal`, `isInline`, `isVertical` for easy layout detection. 72 | - Label styling enhancements. 73 | 74 | ## Version 1.5.4 75 | 76 | **Date**: 22-Sep-2018 77 | 78 | - Refactor code via `kartik\base\BootstrapInterface`. 79 | - (enh #113): Enhance checkbox styling for BS3. 80 | 81 | ## Version 1.5.3 82 | 83 | **Date**: 20-Sep-2018 84 | 85 | - (enh #112): Enhance checkbox styling for enclosed label for both BS4 and BS3. 86 | - (enh #111): Enhance BS3 checkbox styling. 87 | - Enhance to use `Config::hasCssClass`. 88 | - Better styling for Bootstrap 4.x hint block. 89 | - (enh #109): Correct BS3 label styling and rendering for checkboxes and radios. 90 | - (enh #108): Add bootstrap grid column css size map configuration. 91 | - (enh #107): Add bootstrap 4 CSS highlight class for server validation errors. 92 | 93 | ## Version 1.5.2 94 | 95 | **Date**: 05-Sep-2018 96 | 97 | - Add BS4 custom checkbox & custom radio controls support. 98 | - (kartik-v/yii2-krajee-base#94): Refactor code and consolidate / optimize properties within traits. 99 | - Add Bootstrap button default CSS and icon prefix parsing. 100 | - (enh #102): Enhance size modifier detection and input feedback icons. 101 | - (bug #101): Correct `addClass` assignment for HTML5 inputs. 102 | - (enh #100): Control ActiveField addons highlight for success & error states. 103 | - (enh #99): Correct ActiveField wrapper templates when `skipFormLayout` is set to `true`. 104 | 105 | ## Version 1.5.1 106 | 107 | **Date**: 16-Aug-2018 108 | 109 | - (bug #98): Correct ActiveForm css variables init. 110 | 111 | ## Version 1.5.0 112 | 113 | **Date**: 16-Aug-2018 114 | 115 | - Implement AddonTrait. 116 | - (enh #95): Add Bootstrap 4.x Support. 117 | - (bug #94): Add missing comma in activeform css. 118 | - Reorganize source code in `src` directory. 119 | - (enh #91, #92): Correct validation for getting form layout style. 120 | - Set krajee base dependency to v1.9.x. 121 | 122 | ## Version 1.4.9 123 | 124 | **Date**: 05-Mar-2018 125 | 126 | - (enh #89): Optimize and remove redundant code. 127 | - (enh #88): Do not render addon content if empty. 128 | - (enh #83): Correct PHPDoc to ensure correct return value for `ActiveForm::field()` method. 129 | - (enh #82): Allow configuration of `itemOptions` for `checkboxButtonGroup` and `radioButtonGroup`. 130 | - (enh #81): Change visibility of `$_pluginHintKeys`. 131 | - (enh #79, #80): Allow configuration of multiple addons. 132 | - (bug #78): Correct offset CSS class generation for horizontal forms. 133 | - CSS enhancements for addons and other styling validation enhancements. 134 | - Add contribution and issue/PR log templates. 135 | - Enhance PHP Documentation for all classes and methods in the extension. 136 | - (enh #76): Refactor code with additional enhancements for horizontal layout (with code support by Enrica): 137 | - ActiveForm changes 138 | - allow `formConfig` to be changed dynamically between `ActiveForm::begin` and `ActiveForm::end` and move `getFormLayoutStyle` to ActiveField 139 | - ActiveField changes 140 | - set default template moved from ActiveForm to ActiveField.initLayout() 141 | (template and css are properties of an ActiveField) 142 | - Enhance public options `labelSpan` and `deviceSize` on level ActiveField also 143 | - Defaulting of `labelSpan` and `deviceSize` Priority: 1. Option (fieldConfig), 144 | 2. formConfig, 3. _settings (default) 145 | - Build CSS for label, offset and Input on level field 146 | - bug: Fix for checkbox/radio showLabels=>false 147 | - enh: New option `horizontalCssClasses` compatible with yii/bootstrap/ActiveForm with 148 | config options for `wrapper`, `label`, `error`, `hint`. These options give complete 149 | control for all classes. `labelSpan` still works and `wrapper` is added if there 150 | is no `col-` tag defined. 151 | - enh: Add template with `{beginWrapper`}, `{endWrapper}` to enclose input, hint, error 152 | - enh: Optionally template `{label}` could be split into `{beginLabel}`, 153 | `{labelTitle}` and `{endLabel}` tag. `{label}` is still working as usual 154 | - (bug #75): Allow `ActiveForm::fieldConfig` to be configured as Closure. 155 | - (enh #72): Better hint container markup rendering. 156 | 157 | ## Version 1.4.8 158 | 159 | **Date:** 28-Apr-2016 160 | 161 | - (enh #74): Add branch alias for dev-master latest release. 162 | - (bug #73): Correct dependency for `ActiveFormAsset`. 163 | 164 | ## Version 1.4.7 165 | 166 | **Date:** 05-Dec-2015 167 | 168 | - (bug #70): Correct `staticOnly` form render. 169 | - (bug #67, #69): Fix typo for `HINT_DEFAULT`. 170 | 171 | ## Version 1.4.6 172 | 173 | **Date:** 05-Dec-2015 174 | 175 | - (enh #66): Better hint data fetch and code reformatting. Refer [updated docs and demo](http://demos.krajee.com/widget-details/active-field#input-hints). 176 | - (bug #65): Fixes to staticOnly form rendering. 177 | - (enh #64): Enhancement to display and style hints via icon popups or label hover 178 | - (enh #61): Use model `getAttributeLabel()` as default in `initPlaceholder`. 179 | 180 | ## Version 1.4.5 181 | 182 | **Date:** 22-Oct-2015 183 | 184 | - (enh #60): Enhancements to `checkboxButtonGroup` and `radioButtonGroup`. 185 | - (enh #59): Added .gitignore for composer stuff. 186 | 187 | 188 | ## Version 1.4.4 189 | 190 | **Date:** 08-Jul-2015 191 | 192 | - (enh #56): Implement feedback icons within inputs. 193 | 194 | ## Version 1.4.3 195 | 196 | **Date:** 17-Jun-2015 197 | 198 | - (enh #55): Set composer ## Version dependencies. 199 | 200 | ## Version 1.4.2 201 | 202 | **Date:** 11-May-2015 203 | 204 | - (enh #54): Set default ActiveForm field template to be consistent with yii\widgets\ActiveForm. 205 | - (enh #49, #50): Updates to hint rendering for latest yii ActiveField upgrade. 206 | - (enh #48): Various enhancements to Horizontal Form Layout Styles. 207 | - (enh kartik-v/yii2-widgets#243): Enhance CSS style `kv-fieldset-inline`. 208 | - (bug #46): Bootstrap input group addons for horizontal forms. 209 | - (enh #42): New ActiveField property `skipFormLayout` to override and skip special form layout styling. 210 | - (enh #41): New properties for adding or wrapping markup before LABEL, ERROR & HINT blocks. 211 | - (enh #40): Initialize ActiveField template more correctly. 212 | - (enh #39): Change ActiveField private properties to protected. 213 | - (enh #38): Fix `autoPlaceholder` property for INLINE forms when `showLabels` is `true`. 214 | - (enh #37): Scale inputs to full width in horizontal forms when `showLabels` is `ActiveForm:;SCREEN_READER`. 215 | - (enh #36): Prevent offset of checkbox/radio labels for horizontal forms when `enclosedByLabel` is `false`. 216 | - (bug #33): Correct autoPlaceholder based attribute label generation for tabular inputs. 217 | - (enh #32): Create new `checkboxButtonGroup` & `radioButtonGroup` in ActiveField. 218 | 219 | ## Version 1.4.1 220 | 221 | **Date:** 14-Feb-2015 222 | 223 | - (enh #30): Add `control-label` class to labels for Vertical form. 224 | - Set copyright year to current. 225 | 226 | ## Version 1.4.0 227 | 228 | **Date:** 28-Jan-2015 229 | 230 | - (enh #28): Enhancements for error and hint display for horizontal forms. 231 | - (enh #27): New property `staticValue` in ActiveField. 232 | - (enh #26): Enhance `ActiveField::staticInput` to include options to show error and hint. 233 | - (enh #25): Default `showHints` to `true` for all form types in ActiveForm. 234 | - (enh #24): Allow static data forms through new `ActiveForm::staticOnly` property. 235 | - (enh #22): Enhance active field template for controlling labels, hints, & errors. 236 | - (enh #21): Prevent display of error and hint blocks for static input. 237 | - (enh #20): Ability to add markup before and after ActiveField Input. 238 | - (enh #19): Add new `showHints` property to ActiveField configuration. 239 | 240 | ## Version 1.3.0 241 | 242 | **Date:** 04-Dec-2014 243 | 244 | - (enh #13): Allow `showLabels` property in ActiveForm & ActiveField to be tristate: 245 | - `true`: show labels 246 | - `false`: hide labels 247 | - `ActiveForm::SCREEN_READER`: show in screen reader only (hide from normal display) 248 | - (enh #12): Include new `disabled` and `readonly` properties in ActiveForm. 249 | - (enh #9): Enhance support for labels and horizontal form layouts 250 | - Allow labels to be set to `false` to hide them completely 251 | - Enhance HORIZONTAL forms to style labels appropriately when they are blank/empty. 252 | - Enhance HORIZONTAL forms to style labels, hints, and errors appropriately when they are set to false to fill the container width 253 | 254 | ## Version 1.2.0 255 | 256 | **Date:** 26-Nov-2014 257 | 258 | - (bug #7): Fix custom labels rendering for checkboxes 259 | - Set release to stable 260 | 261 | ## Version 1.1.0 262 | 263 | **Date:** 17-Nov-2014 264 | 265 | - (enh #6): Fix incorrect alignment of inputs, buttons, and error block for INLINE FORM orientation. 266 | - (enh #5): Add special styling for bootstrap input group button addons for success and error states. 267 | - Clean up invalid assets, unneeded classes, and refactor code. 268 | - (enh #1): Enhance ActiveField inputs to include bootstrap default styles. 269 | 270 | ## Version 1.0.0 271 | 272 | **Date:** 08-Nov-2014 273 | 274 | - Initial release 275 | - Sub repo split from [yii2-widgets](https://github.com/kartik-v/yii2-widgets) -------------------------------------------------------------------------------- /src/ActiveForm.php: -------------------------------------------------------------------------------- 1 | 'form-signup', 31 | * 'type' => ActiveForm::TYPE_HORIZONTAL 32 | * ]); 33 | * // Inline Form 34 | * $form = ActiveForm::begin([ 35 | * 'id' => 'form-login', 36 | * 'type' => ActiveForm::TYPE_INLINE 37 | * 'fieldConfig' => ['autoPlaceholder'=>true] 38 | * ]); 39 | * // Horizontal Form Configuration 40 | * $form = ActiveForm::begin([ 41 | * 'id' => 'form-signup', 42 | * 'type' => ActiveForm::TYPE_HORIZONTAL 43 | * 'formConfig' => ['labelSpan' => 2, 'deviceSize' => ActiveForm::SIZE_SMALL] 44 | * ]); 45 | * ``` 46 | * 47 | * @method ActiveField field(Model $model, string $attribute, array $options = []) 48 | * 49 | * @author Kartik Visweswaran 50 | * @since 1.0 51 | */ 52 | class ActiveForm extends YiiActiveForm implements BootstrapInterface 53 | { 54 | use BootstrapTrait; 55 | 56 | /** 57 | * @var bool whether to render tooltip styled error and success messages. Applicable only for [[bsVersion]] >= 4. 58 | */ 59 | public $tooltipStyleFeedback = false; 60 | 61 | /** 62 | * @var int the default label span for horizontal forms which will offset the adjacent input accordingly. 63 | */ 64 | const DEFAULT_LABEL_SPAN = 2; 65 | 66 | /** 67 | * @var int the bootstrap default full grid width. 68 | */ 69 | const FULL_SPAN = 12; 70 | 71 | /** 72 | * @var string bootstrap styled vertical form layout (this is the default style) 73 | */ 74 | const TYPE_VERTICAL = 'vertical'; 75 | 76 | /** 77 | * @var string bootstrap styled horizontal form layout 78 | */ 79 | const TYPE_HORIZONTAL = 'horizontal'; 80 | 81 | /** 82 | * @var string bootstrap styled inline form layout 83 | */ 84 | const TYPE_INLINE = 'inline'; 85 | 86 | /** 87 | * @var string floating labels form layout (supported only for bootstrap 5.x) 88 | */ 89 | const TYPE_FLOATING = 'floating'; 90 | 91 | /** 92 | * @var string bootstrap screen reader style for labels 93 | */ 94 | const SCREEN_READER = 'sr-only'; 95 | 96 | /** 97 | * @inheritdoc 98 | */ 99 | public $fieldClass = 'kartik\form\ActiveField'; 100 | 101 | /** 102 | * @var string form orientation type (for bootstrap styling). Either [[TYPE_VERTICAL]], [[TYPE_HORIZONTAL]], or 103 | * [[TYPE_INLINE]]. Defaults to [[TYPE_VERTICAL]]. 104 | */ 105 | public $type; 106 | 107 | /** 108 | * @var integer the bootstrap grid width. Defaults to [[FULL_SPAN]]. 109 | */ 110 | public $fullSpan = self::FULL_SPAN; 111 | 112 | /** 113 | * @var array the configuration for the form. Takes in the following properties 114 | * - `labelSpan`: _integer_, the bootstrap grid column width (usually between 1 to 12) 115 | * - `deviceSize`: _string_, one of the bootstrap sizes (refer the ActiveForm::SIZE constants) 116 | * - `showLabels`: _boolean_|_string_, whether to show labels (true)_, hide labels (false)_, or display only for 117 | * [[SCREEN_READER]]. This is mainly useful for inline forms. 118 | * - `showErrors`: _boolean_, whether to show errors (true) or hide errors (false). This is mainly useful for inline 119 | * forms. 120 | * - `showHints`: _boolean_, whether to show hints (true) or hide errors (false). Defaults to `true`. The hint will be 121 | * rendered only if a valid hint has been set through the `hint()` method. 122 | * 123 | * Defaults to, 124 | * 125 | * ```php 126 | * [ 127 | * 'labelSpan' => 2, 128 | * 'deviceSize' => ActiveForm::SIZE_MEDIUM, 129 | * 'showLabels' => true, 130 | * 'showErrors' => true, 131 | * 'showHints' => true 132 | * ], 133 | * ``` 134 | */ 135 | public $formConfig = []; 136 | 137 | /** 138 | * @var array HTML attributes for the form tag. Defaults to: 139 | * 140 | * ```php 141 | * ['role' => 'form'] 142 | * ``` 143 | */ 144 | public $options = ['role' => 'form']; 145 | 146 | /** 147 | * @var array|string CSS classes that will be appended for inline form. 148 | * Defaults to `['row', 'row-cols-lg-auto', 'g-3', 'align-items-center']` for Bootstrap 5.x and 149 | * empty string for others. 150 | */ 151 | public $inlineFormCssClass; 152 | 153 | /** 154 | * @var boolean|Closure whether all inputs in form are to be rendered as bootstrap 5 static text inputs. For 155 | * advanced configuration (e.g. dynamically setting different flags for different fields), this can be setup as a 156 | * Closure callback. When setup as a Closure callback, you can receive the model instance and the active field 157 | * object instance as parameters. For example: 158 | * 159 | * ``` 160 | * 'staticOnly' => function ($model, $field) { 161 | * return in_array($field->attribute, $model->staticOnlyAttributes()); 162 | * } 163 | * ``` 164 | */ 165 | public $staticOnly = false; 166 | 167 | /** 168 | * @var boolean|Closure whether all inputs in form are to be disabled. For advanced configuration (e.g. dynamically 169 | * setting different flags for different fields), this can be setup as a Closure callback. When setup as a Closure 170 | * callback, you can receive the model instance and the active field object instance as parameters. For example: 171 | * 172 | * ``` 173 | * 'disabled' => function ($model, $field) { 174 | * return in_array($field->attribute, $model->disabledAttributes()); 175 | * } 176 | * ``` 177 | */ 178 | public $disabled = false; 179 | 180 | /** 181 | * @var boolean|Closure whether all inputs in form are to be readonly. For advanced configuration (e.g. dynamically 182 | * setting different flags for different fields), this can be setup as a Closure callback. When setup as a Closure 183 | * callback, you can receive the model instance and the active field object instance as parameters. For example: 184 | * 185 | * ``` 186 | * 'readonly' => function ($model, $field) { 187 | * return in_array($field->attribute, $model->readonlyAttributes()); 188 | * } 189 | * ``` 190 | */ 191 | public $readonly = false; 192 | 193 | /** 194 | * @var array the default form configuration. 195 | */ 196 | private $_config = [ 197 | self::TYPE_VERTICAL => [ 198 | 'showLabels' => true, // show or hide labels (mainly useful for inline type form) 199 | 'showErrors' => true, // show or hide errors (mainly useful for inline type form) 200 | 'showHints' => true // show or hide hints below the input 201 | ], 202 | self::TYPE_HORIZONTAL => [ 203 | 'showLabels' => true, 204 | 'showErrors' => true, 205 | 'showHints' => true, 206 | ], 207 | self::TYPE_INLINE => [ 208 | 'showLabels' => self::SCREEN_READER, 209 | 'showErrors' => false, 210 | 'showHints' => true, 211 | ], 212 | self::TYPE_FLOATING => [ 213 | 'showLabels' => true, 214 | 'showErrors' => true, 215 | 'showHints' => true, 216 | ], 217 | ]; 218 | 219 | /** 220 | * @inheritdoc 221 | * @throws InvalidConfigException 222 | */ 223 | public function init() 224 | { 225 | $this->initBsVersion(); 226 | if (!is_int($this->fullSpan) || $this->fullSpan < 1) { 227 | throw new InvalidConfigException("The 'fullSpan' property must be a valid positive integer."); 228 | } 229 | $this->initForm(); 230 | parent::init(); 231 | $this->registerAssets(); 232 | } 233 | 234 | /** 235 | * Gets form layout style configuration. This method is used by [[\kartik\field\FieldRange]] widget. 236 | * 237 | * @return array the form layout style configuration. 238 | */ 239 | public function getFormLayoutStyle() 240 | { 241 | $config = $this->formConfig; 242 | $span = ArrayHelper::getValue($config, 'labelSpan', ActiveField::NOT_SET); 243 | $size = ArrayHelper::getValue($config, 'deviceSize', ActiveField::NOT_SET); 244 | $labelCss = $inputCss = ActiveField::NOT_SET; 245 | $iSpan = intval($span); 246 | if ($span != ActiveField::NOT_SET && $iSpan > 0) { 247 | // validate if invalid `labelSpan` is passed else set to [[DEFAULT_LABEL_SPAN]] 248 | if ($iSpan <= 0 && $iSpan >= $this->fullSpan) { 249 | $iSpan = self::DEFAULT_LABEL_SPAN; 250 | } 251 | 252 | // validate if invalid `deviceSize` is passed else set to [[SIZE_MEDIUM]] 253 | if ($size == ActiveField::NOT_SET) { 254 | $size = self::SIZE_MEDIUM; 255 | } 256 | 257 | $prefix = "col-{$size}-"; 258 | $labelCss = $prefix.$iSpan; 259 | $inputCss = $prefix.($this->fullSpan - $iSpan); 260 | } 261 | 262 | return ['labelCss' => $labelCss, 'inputCss' => $inputCss]; 263 | } 264 | 265 | /** 266 | * Registers the assets for the [[ActiveForm]] widget. 267 | * @throws InvalidConfigException|Exception 268 | */ 269 | public function registerAssets() 270 | { 271 | $view = $this->getView(); 272 | ActiveFormAsset::register($view); 273 | $id = 'jQuery("#'.$this->options['id'].' .kv-hint-special")'; 274 | $js = 'var $el='.$id.';if($el.length){$el.each(function(){$(this).activeFieldHint()});}'; 275 | if (!$this->isBs(3)) { 276 | $js .= "kvBs4InitForm();"; 277 | } 278 | $view->registerJs($js); 279 | } 280 | 281 | /** 282 | * Whether an inline layout form 283 | * @return bool 284 | */ 285 | public function isInline() 286 | { 287 | return $this->type === self::TYPE_INLINE; 288 | } 289 | 290 | /** 291 | * Whether a horizontal layout form 292 | * @return bool 293 | */ 294 | public function isHorizontal() 295 | { 296 | return $this->type === self::TYPE_HORIZONTAL; 297 | } 298 | 299 | /** 300 | * Whether a vertical layout form 301 | * @return bool 302 | */ 303 | public function isVertical() 304 | { 305 | return !$this->isHorizontal() && !$this->isInline(); 306 | } 307 | 308 | /** 309 | * Initializes the form configuration array and parameters for the form. 310 | * 311 | * @throws InvalidConfigException 312 | */ 313 | protected function initForm() 314 | { 315 | if (empty($this->type)) { 316 | $this->type = self::TYPE_VERTICAL; 317 | } 318 | if (!in_array($this->type, 319 | [self::TYPE_VERTICAL, self::TYPE_HORIZONTAL, self::TYPE_INLINE, self::TYPE_FLOATING])) { 320 | throw new InvalidConfigException('Invalid layout type: '.$this->type); 321 | } 322 | $bsVer = $this->getBsVer(); 323 | $this->formConfig = array_replace_recursive($this->_config[$this->type], $this->formConfig); 324 | if ($this->isInline()) { 325 | if ($bsVer === 5 && !isset($this->inlineFormCssClass)) { 326 | $this->inlineFormCssClass = ['row', 'row-cols-lg-auto', 'g-3', 'align-items-center']; 327 | } 328 | if (!empty($this->inlineFormCssClass)) { 329 | Html::addCssClass($this->options, $this->inlineFormCssClass); 330 | } 331 | } 332 | $css = ["form-{$this->type}"]; 333 | if ($this->isHorizontal()) { 334 | $css[] = 'kv-form-horizontal'; 335 | } 336 | $formCss = 'kv-form-bs3'; 337 | if ($bsVer !== 3) { 338 | $formCss = 'kv-form-bs4'; 339 | if ($this->tooltipStyleFeedback) { 340 | $css[] = 'tooltip-feedback'; 341 | } 342 | } 343 | $css[] = $formCss; 344 | Html::addCssClass($this->options, $css); 345 | } 346 | 347 | /** 348 | * Gets Screen Reader Only CSS class 349 | * @return string 350 | * @throws Exception 351 | */ 352 | public function getSrOnlyCss() 353 | { 354 | return $this->getCssClass(self::BS_SR_ONLY); 355 | } 356 | } 357 | -------------------------------------------------------------------------------- /src/ActiveField.php: -------------------------------------------------------------------------------- 1 | field($model, 'email', ['addon' => ['type'=>'prepend', 'content'=>'@']]); 32 | * echo $form->field($model, 'amount_paid', ['addon' => ['type'=>'append', 'content'=>'.00']]); 33 | * echo $form->field($model, 'phone', [ 34 | * 'addon' => [ 35 | * 'type'=>'prepend', 36 | * 'content'=>'' 37 | * ] 38 | * ]); 39 | * ``` 40 | * 41 | * Usage example with horizontal form and advanced field layout CSS configuration: 42 | * 43 | * ```php 44 | * echo $form->field($model, 'email', ['labelSpan' => 2, 'deviceSize' => ActiveForm::SIZE_SMALL]]); 45 | * echo $form->field($model, 'amount_paid', ['horizontalCssClasses' => ['wrapper' => 'hidden-xs']]); 46 | * echo $form->field($model, 'phone', [ 47 | * 'horizontalCssClasses' => ['label' => 'col-md-2 col-sm-3 col-xs-12 myRedClass'] 48 | * ]); 49 | * echo $form->field($model, 'special', [ 50 | * 'template' => '{beginLabel}For: {labelTitle}{endLabel}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}' 51 | * ]); 52 | * ``` 53 | * 54 | * @property ActiveForm $form 55 | * @author Kartik Visweswaran 56 | * @since 1.0 57 | */ 58 | class ActiveField extends YiiActiveField 59 | { 60 | use AddonTrait; 61 | 62 | /** 63 | * @var string an empty string value 64 | */ 65 | const NOT_SET = ''; 66 | 67 | /** 68 | * @var string HTML radio input type 69 | */ 70 | const TYPE_RADIO = 'radio'; 71 | 72 | /** 73 | * @var string HTML checkbox input type 74 | */ 75 | const TYPE_CHECKBOX = 'checkbox'; 76 | 77 | /** 78 | * @var string the default height for the Krajee multi select input 79 | */ 80 | const MULTI_SELECT_HEIGHT = '145px'; 81 | 82 | /** 83 | * @var string default hint type that is displayed below the input 84 | */ 85 | const HINT_DEFAULT = 1; 86 | 87 | /** 88 | * @var string special hint type that allows display via an indicator icon or on hover/click of the field label 89 | */ 90 | const HINT_SPECIAL = 2; 91 | 92 | /** 93 | * @var array the list of hint keys that will be used by ActiveFieldHint jQuery plugin 94 | */ 95 | protected static $_pluginHintKeys = [ 96 | 'iconCssClass', 97 | 'labelCssClass', 98 | 'contentCssClass', 99 | 'hideOnEscape', 100 | 'hideOnClickOut', 101 | 'title', 102 | 'placement', 103 | 'container', 104 | 'animation', 105 | 'delay', 106 | 'template', 107 | 'selector', 108 | 'viewport', 109 | ]; 110 | 111 | /** 112 | * @var boolean whether to override the form layout styles and skip field formatting as per the form layout. 113 | * Defaults to `false`. 114 | */ 115 | public $skipFormLayout = false; 116 | 117 | /** 118 | * @var bool whether to auto offset toggle inputs (checkboxes / radios) horizontal form layout for BS 4.x forms. 119 | * This will read the `labelSpan` and automatically offset the checkboxes/radios. 120 | */ 121 | public $autoOffset = true; 122 | 123 | /** 124 | * @var bool whether to render the wrapper in the template if [[wrapperOptions]] is empty. 125 | */ 126 | public $renderEmptyWrapper = false; 127 | 128 | /** 129 | * @inheritdoc 130 | */ 131 | public $labelOptions = []; 132 | 133 | /** 134 | * @var integer the hint display type. If set to `self::HINT_DEFAULT`, the hint will be displayed as a text block below 135 | * each input. If set to `self::HINT_SPECIAL`, then the `hintSettings` will be applied to display the field 136 | * hint. 137 | */ 138 | public $hintType = self::HINT_DEFAULT; 139 | 140 | /** 141 | * @var array the settings for displaying the hint. These settings are parsed only if `hintType` is set to 142 | * `self::HINT_SPECIAL`. The following properties are supported: 143 | * - `showIcon`: _boolean_, whether to display the hint via a help icon indicator. Defaults to `true`. 144 | * - `icon`: _string_, the markup to display the help icon. Defaults to 145 | * - `` for Bootstrap 3.x. 146 | * - `` for Bootstrap 4.x and above. 147 | * - `iconBesideInput`: _boolean_, whether to display the icon beside the input. Defaults to `false`. The following 148 | * actions will be taken based on this setting: 149 | * - if set to `false` the help icon is displayed beside the label and the `labelTemplate` setting is used to 150 | * render the icon and label markups. 151 | * - if set to `true` the help icon is displayed beside the input and the `inputTemplate` setting is used to 152 | * render the icon and input markups. 153 | * - `labelTemplate`: _string_, the template to render the help icon and the field label. Defaults to `{label}{help}`, 154 | * where 155 | * - `{label}` will be replaced by the ActiveField label content 156 | * - `{help}` will be replaced by the help icon indicator markup 157 | * - `inputTemplate`: _string_, the template to render the help icon and the field input. Defaults to `'
{input}
{help}
',`, where 159 | * - `{input}` will be replaced by the ActiveField input content 160 | * - `{help}` will be replaced by the help icon indicator markup 161 | * - `onLabelClick`: _boolean_, whether to display the hint on clicking the label. Defaults to `false`. 162 | * - `onLabelHover`: _boolean_, whether to display the hint on hover of the label. Defaults to `true`. 163 | * - `onIconClick`: _boolean_, whether to display the hint on clicking the icon. Defaults to `true`. 164 | * - `onIconHover`: _boolean_, whether to display the hint on hover of the icon. Defaults to `false`. 165 | * - `iconCssClass`: _string_, the CSS class appended to the `span` container enclosing the icon. 166 | * - `labelCssClass`: _string_, the CSS class appended to the `span` container enclosing label text within label tag. 167 | * - `contentCssClass`: _string_, the CSS class appended to the `span` container displaying help content within 168 | * popover. 169 | * - `hideOnEscape`: _boolean_, whether to hide the popover on clicking escape button on the keyboard. Defaults to `true`. 170 | * - `hideOnClickOut`: _boolean_, whether to hide the popover on clicking outside the popover. Defaults to `true`. 171 | * - `title`: _string_, the title heading for the popover dialog. Defaults to empty string, whereby the heading is not 172 | * displayed. 173 | * - `placement`: _string_, the placement of the help popover on hover or click of the icon or label. Defaults to 174 | * `top`. 175 | * - `container`: _string_, the specific element to which the popover will be appended to. Defaults to `table` when 176 | * `iconBesideInput` is `true`, else defaults to `form` 177 | * - `animation`: _boolean_, whether to add a CSS fade transition effect when opening and closing the popover. Defaults to 178 | * `true`. 179 | * - `delay``: _integer_|_array_, the number of milliseconds it will take to open and close the popover. Defaults to `0`. 180 | * - `selector`: _integer_, the specified selector to add the popover to. Defaults to boolean `false`. 181 | * - `viewport`: _string_|_array_, the element within which the popover will be bounded to. Defaults to 182 | * `['selector' => 'body', 'padding' => 0]`. 183 | */ 184 | public $hintSettings = []; 185 | 186 | /** 187 | * @var array the feedback icon configuration (applicable for [bootstrap text inputs](http://getbootstrap.com/css/#with-optional-icons)). 188 | * This must be setup as an array containing the following keys: 189 | * 190 | * - `type`: _string_, the icon type to use. Should be one of `raw` or `icon`. Defaults to `icon`, where the `default`, 191 | * `error` and `success` settings will be treated as an icon CSS suffix name. If set to `raw`, they will be 192 | * treated as a raw content markup. 193 | * - `prefix`: _string_, the icon CSS class prefix to use if `type` is `icon`. Defaults to `glyphicon glyphicon-` for 194 | * Bootstrap 3.x and `fas fa-` for Bootstrap 4.x and above. 195 | * - `default`: _string_, the icon (CSS class suffix name or raw markup) to show by default. If not set will not be 196 | * shown. 197 | * - `error`: _string_, the icon (CSS class suffix name or raw markup) to use when input has an error validation. If 198 | * not set will not be shown. 199 | * - `success`: _string_, the icon (CSS class suffix name or raw markup) to use when input has a success validation. If 200 | * not set will not be shown. 201 | * - `defaultOptions`: _array_, the HTML attributes to apply for default icon. The special attribute `description` can 202 | * be set to describe this feedback as an `aria` attribute for accessibility. Defaults to `(default)`. 203 | * - `errorOptions`: _array_, the HTML attributes to apply for error icon. The special attribute `description` can be 204 | * set to describe this feedback as an `aria` attribute for accessibility. Defaults to `(error)`. 205 | * - `successOptions`: _array_, the HTML attributes to apply for success icon. The special attribute `description` can 206 | * be set to describe this feedback as an `aria` attribute for accessibility. Defaults to `(success)`. 207 | * 208 | * @see http://getbootstrap.com/css/#with-optional-icons 209 | */ 210 | public $feedbackIcon = []; 211 | 212 | /** 213 | * @var string content to be placed before field within the form group at the beginning 214 | */ 215 | public $contentBeforeField = ''; 216 | 217 | /** 218 | * @var string content to be placed after field within the form group at the end 219 | */ 220 | public $contentAfterField = ''; 221 | 222 | /** 223 | * @var string content to be placed before label 224 | */ 225 | public $contentBeforeLabel = ''; 226 | 227 | /** 228 | * @var string content to be placed after label 229 | */ 230 | public $contentAfterLabel = ''; 231 | 232 | /** 233 | * @var string content to be placed before input 234 | */ 235 | public $contentBeforeInput = ''; 236 | 237 | /** 238 | * @var string content to be placed after input 239 | */ 240 | public $contentAfterInput = ''; 241 | 242 | /** 243 | * @var string content to be placed before error block 244 | */ 245 | public $contentBeforeError = ''; 246 | 247 | /** 248 | * @var string content to be placed after error block 249 | */ 250 | public $contentAfterError = ''; 251 | 252 | /** 253 | * @var string content to be placed before hint block 254 | */ 255 | public $contentBeforeHint = ''; 256 | 257 | /** 258 | * @var string content to be placed after hint block 259 | */ 260 | public $contentAfterHint = ''; 261 | 262 | /** 263 | * @var string the template for rendering the Bootstrap 4.x custom file browser control 264 | * @see https://getbootstrap.com/docs/4.1/components/forms/#file-browser 265 | */ 266 | public $customFileTemplate = "
\n{input}\n{label}\n
\n{error}\n{hint}"; 267 | 268 | /** 269 | * @var string the template for rendering checkboxes and radios for a default Bootstrap markup without an enclosed 270 | * label 271 | */ 272 | public $checkTemplate = "{input}\n{label}\n{error}\n{hint}"; 273 | 274 | /** 275 | * @var string the template for rendering checkboxes and radios for a default Bootstrap markup with an enclosed 276 | * label 277 | */ 278 | public $checkEnclosedTemplate = "{beginLabel}\n{input}\n{labelTitle}\n{endLabel}\n{error}\n{hint}"; 279 | 280 | /** 281 | * @var array the HTML attributes for the container wrapping BS4 checkbox or radio controls within which the content 282 | * will be rendered via the [[checkTemplate]] or [[checkEnclosedTemplate]] 283 | */ 284 | public $checkWrapperOptions = []; 285 | 286 | /** 287 | * @var bool whether to highlight error and success states on input group addons automatically 288 | */ 289 | public $highlightAddon = true; 290 | 291 | /** 292 | * @var string CSS classname to add to the input 293 | */ 294 | public $addClass = 'form-control'; 295 | 296 | /** 297 | * @var string the static value for the field to be displayed for the static input OR when the form is in 298 | * staticOnly mode. This value is not HTML encoded. 299 | */ 300 | public $staticValue; 301 | 302 | /** 303 | * @var boolean|string whether to show labels for the field. Should be one of the following values: 304 | * - `true`: show labels for the field 305 | * - `false`: hide labels for the field 306 | * - `ActiveForm::SCREEN_READER`: show in screen reader only (hide from normal display) 307 | */ 308 | public $showLabels; 309 | 310 | /** 311 | * @var boolean whether to show errors for the field 312 | */ 313 | public $showErrors; 314 | 315 | /** 316 | * @var boolean whether to show hints for the field 317 | */ 318 | public $showHints; 319 | 320 | /** 321 | * @var boolean whether to show required asterisk/star indicator after each field label when the model attribute is 322 | * set to have a `required` validation rule. This will add a CSS class `has-star` to the label and show the required 323 | * asterisk/star after the label based on CSS `::after` styles. If you want any other label markup to show a 324 | * required asterisk for a required model attribute field, then just add the CSS class `has-star` to the label/span 325 | * markup element within the active field container with CSS class `form-group`. 326 | */ 327 | public $showRequiredIndicator = true; 328 | 329 | /** 330 | * @var boolean whether the label is to be hidden and auto-displayed as a placeholder 331 | */ 332 | public $autoPlaceholder; 333 | 334 | /** 335 | * @var array options for the wrapper tag, used in the `{beginWrapper}` token within [[template]]. 336 | */ 337 | public $wrapperOptions = []; 338 | 339 | /** 340 | * @var string inherits and overrides values from parent class. The value can be overridden within 341 | * [[ActiveForm::field()]] method. The following tokens are supported: 342 | * - `{beginLabel}`: Container begin tag for labels (to be used typically along with `{labelTitle}` token 343 | * when you do not wish to directly use the `{label}` token) 344 | * - `{labelTitle}`: Label content without tags (to be used typically when you do not wish to directly use 345 | * the `{label` token) 346 | * - `{endLabel}`: Container end tag for labels (to be used typically along with `{labelTitle}` token 347 | * when you do not wish to directly use the `{label}` token) 348 | * - `{label}`: Full label tag with begin tag, content and end tag 349 | * - `{beginWrapper}`: Container for input,error and hint start tag. Uses a `
` tag if there is a input wrapper 350 | * CSS detected, else defaults to empty string. 351 | * - `{input}`: placeholder for input control whatever it is 352 | * - `{hint}`: placeholder for hint/help text including sub container 353 | * - `{error}`: placeholder for error text including sub container 354 | * - `{endWrapper}`: end tag for `{beginWrapper}`. Defaults to `
` if there is a input wrapper CSS detected, 355 | * else defaults to empty string. 356 | */ 357 | public $template = "{label}\n{beginWrapper}\n{input}\n{hint}\n{error}\n{endWrapper}"; 358 | 359 | /** 360 | * 361 | * @var integer the bootstrap grid column width (usually between 1 to 12) 362 | */ 363 | public $labelSpan; 364 | 365 | /** 366 | * 367 | * @var string one of the bootstrap sizes (refer the ActiveForm::SIZE constants) 368 | */ 369 | public $deviceSize; 370 | 371 | /** 372 | * @var boolean whether to render the error. Default is `true` except for layout `inline`. 373 | */ 374 | public $enableError; 375 | 376 | /** 377 | * @var boolean whether to render the label. Default is `true`. 378 | */ 379 | public $enableLabel; 380 | 381 | /** 382 | * @var null|array CSS grid classes for horizontal layout. This must be an array with these keys: 383 | * - `offset`: the offset grid class to append to the wrapper if no label is rendered 384 | * - `label`: the label grid class 385 | * - `wrapper`: the wrapper grid class 386 | * - `error`: the error grid class 387 | * - `hint`: the hint grid class 388 | * These options are compatible and similar to [[\yii\bootstrap\ActiveForm]] and provide a complete flexible 389 | * container. If `labelSpan` is set in [[ActiveForm::formConfig]] and `wrapper` is also set, then both css options 390 | * are concatenated. If `wrapper` contains a 'col-' class wrapper, it overrides the tag from `labelSpan`. 391 | */ 392 | public $horizontalCssClasses; 393 | 394 | /** 395 | * @var boolean whether the input is to be offset (like for checkbox or radio). 396 | */ 397 | protected $_offset = false; 398 | 399 | /** 400 | * @var boolean the container for multi select 401 | */ 402 | protected $_multiselect = ''; 403 | 404 | /** 405 | * @var boolean is it a static input 406 | */ 407 | protected $_isStatic = false; 408 | 409 | /** 410 | * @var array the settings for the active field layout 411 | */ 412 | protected $_settings = [ 413 | 'input' => '{input}', 414 | 'error' => '{error}', 415 | 'hint' => '{hint}', 416 | 'showLabels' => true, 417 | 'showErrors' => true, 418 | 'labelSpan' => ActiveForm::DEFAULT_LABEL_SPAN, 419 | 'deviceSize' => ActiveForm::SIZE_MEDIUM, 420 | ]; 421 | 422 | /** 423 | * @var boolean whether there is a feedback icon configuration set 424 | */ 425 | protected $_hasFeedback = false; 426 | 427 | /** 428 | * @var boolean whether there is a feedback icon configuration set 429 | */ 430 | protected $_isHintSpecial = false; 431 | 432 | /** 433 | * @var string the label additional css class for horizontal forms and special inputs like checkbox and radio. 434 | */ 435 | private $_labelCss; 436 | 437 | /** 438 | * @var string the input container additional css class for horizontal forms and special inputs like checkbox and 439 | * radio. 440 | */ 441 | private $_inputCss; 442 | 443 | /** 444 | * @var boolean whether the hint icon is beside the input. 445 | */ 446 | private $_iconBesideInput = false; 447 | 448 | /** 449 | * @var string the identifier for the hint popover container. 450 | */ 451 | private $_hintPopoverContainer; 452 | 453 | /** 454 | * {@inheritdoc} 455 | */ 456 | public function __construct($config = []) 457 | { 458 | $layoutConfig = $this->createLayoutConfig($config); 459 | $config = ArrayHelper::merge($layoutConfig, $config); 460 | /* 461 | if ($config['form']->type === ActiveForm::TYPE_HORIZONTAL) { 462 | unset($config['model'], $config['form']); 463 | die('

Layout Config

'.print_r($layoutConfig, true).'

Config

'.print_r($config,
 464 |                     true).'
'); 465 | } 466 | */ 467 | parent::__construct($config); 468 | } 469 | 470 | /** 471 | * Create layout specific configuration 472 | * @param array $instanceConfig the configuration passed to this instance's constructor 473 | * @return array the layout specific default configuration for this instance 474 | */ 475 | protected function createLayoutConfig($instanceConfig = []) 476 | { 477 | $form = $instanceConfig['form']; 478 | $layout = $form->type; 479 | $bsVer = $form->getBsVer(); 480 | $config = [ 481 | 'hintOptions' => ['tag' => 'div', 'class' => ['hint-block']], 482 | 'errorOptions' => ['tag' => 'div', 'class' => 'invalid-feedback'], 483 | 'inputOptions' => ['class' => 'form-control'], 484 | 'labelOptions' => ['class' => ['form-label']], 485 | 'options' => ['class' => $bsVer === 5 ? 'mb-3' : 'form-group'], 486 | ]; 487 | if ($bsVer === 4) { 488 | $config['labelOptions'] = ['class' => []]; 489 | } elseif ($bsVer === 3) { 490 | $config['errorOptions'] = ['tag' => 'div', 'class' => 'help-block help-block-error']; 491 | } 492 | if ($layout === ActiveForm::TYPE_HORIZONTAL) { 493 | $config['template'] = "{label}\n{beginWrapper}\n{input}\n{error}\n{hint}\n{endWrapper}"; 494 | $config['wrapperOptions'] = $config['labelOptions'] = []; 495 | $cssClasses = [ 496 | 'offset' => $bsVer === 3 ? 'col-sm-offset-3' : ['col-sm-10', 'offset-sm-2'], 497 | 'field' => $bsVer > 3 ? 'row' : 'form-group', 498 | ]; 499 | if (isset($instanceConfig['horizontalCssClasses'])) { 500 | $cssClasses = ArrayHelper::merge($cssClasses, $instanceConfig['horizontalCssClasses']); 501 | } 502 | $config['horizontalCssClasses'] = $cssClasses; 503 | foreach (array_keys($cssClasses) as $cfg) { 504 | $key = $cfg === 'field' ? 'options' : "{$cfg}Options"; 505 | if ($cfg !== 'offset' && !empty($cssClasses[$cfg])) { 506 | Html::addCssClass($config[$key], $cssClasses[$cfg]); 507 | } 508 | } 509 | } elseif ($layout === ActiveForm::TYPE_INLINE) { 510 | $config['inputOptions']['placeholder'] = true; 511 | Html::addCssClass($config['options'], 'col-12'); 512 | Html::addCssClass($config['labelOptions'], ['screenreader' => $form->getSrOnlyCss()]); 513 | } elseif ($bsVer === 5 && $layout === ActiveForm::TYPE_FLOATING) { 514 | $config['inputOptions']['placeholder'] = true; 515 | $config['template'] = "{input}\n{label}\n{error}\n{hint}"; 516 | Html::addCssClass($config['options'], ['layout' => 'form-floating mt-3']); 517 | } 518 | 519 | return $config; 520 | } 521 | 522 | /** 523 | * @inheritdoc 524 | */ 525 | public function begin() 526 | { 527 | if ($this->_hasFeedback) { 528 | Html::addCssClass($this->options, 'has-feedback'); 529 | } 530 | 531 | return parent::begin().$this->contentBeforeField; 532 | } 533 | 534 | /** 535 | * @inheritdoc 536 | */ 537 | public function end() 538 | { 539 | return $this->contentAfterField.parent::end(); 540 | } 541 | 542 | /** 543 | * @inheritdoc 544 | * @throws InvalidConfigException 545 | */ 546 | public function init() 547 | { 548 | parent::init(); 549 | $this->initActiveField(); 550 | } 551 | 552 | /** 553 | * Renders a checkbox. This method will generate the "checked" tag attribute according to the model attribute value. 554 | * 555 | * @param array $options the tag options in terms of name-value pairs. The following options are specially 556 | * handled: 557 | * 558 | * - `custom`: _bool_, whether to render bootstrap 4.x custom checkbox styled control. Defaults to `false`. 559 | * This is applicable only for Bootstrap 4.x forms. 560 | * - `switch`: _bool_, whether to render bootstrap 4.x custom switch styled control. Defaults to `false`. 561 | * This is applicable only for Bootstrap 4.x forms. 562 | * @param bool|null $enclosedByLabel whether to enclose the radio within the label. If `true`, the method will 563 | * still use [[template]] to layout the checkbox and the error message except that the radio is enclosed by 564 | * the label tag. 565 | * 566 | * @return ActiveField object 567 | * @throws InvalidConfigException 568 | * @see https://getbootstrap.com/docs/4.1/components/forms/#checkboxes-and-radios-1 569 | * - `uncheck`: _string_, the value associated with the uncheck state of the checkbox. If not set, it will take 570 | * the default value `0`. This method will render a hidden input so that if the checkbox is not checked and is 571 | * submitted, the value of this attribute will still be submitted to the server via the hidden input. 572 | * - `label`: _string_, a label displayed next to the checkbox. It will NOT be HTML-encoded. Therefore you can 573 | * pass in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] 574 | * it to prevent XSS attacks. When this option is specified, the checkbox will be enclosed by a label tag. 575 | * - `labelOptions`: _array_, the HTML attributes for the label tag. This is only used when the "label" option is 576 | * specified. 577 | * - `container: boolean|array, the HTML attributes for the checkbox container. If this is set to false, no 578 | * container will be rendered. The special option `tag` will be recognized which defaults to `div`. This 579 | * defaults to: 580 | * `['tag' => 'div', 'class'=>'radio']` 581 | * The rest of the options will be rendered as the attributes of the resulting tag. The values will be 582 | * HTML-encoded using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. 583 | * 584 | */ 585 | public function checkbox($options = [], $enclosedByLabel = null) 586 | { 587 | return $this->getToggleField(self::TYPE_CHECKBOX, $options, $enclosedByLabel); 588 | } 589 | 590 | /** 591 | * Renders a list of checkboxes. A checkbox list allows multiple selection, like [[listBox()]]. As a result, the 592 | * corresponding submitted value is an array. The selection of the checkbox list is taken from the value of the 593 | * model attribute. 594 | * 595 | * @param array $items the data item used to generate the checkboxes. The array values are the labels, while the 596 | * array keys are the corresponding checkbox values. Note that the labels will NOT be HTML-encoded, while the 597 | * values will be encoded. 598 | * @param array $options options (name => config) for the checkbox list. The following options are specially 599 | * handled: 600 | * 601 | * - `custom`: _bool_, whether to render bootstrap 4.x custom checkbox/radio styled control. Defaults to `false`. 602 | * This is applicable only for Bootstrap 4.x forms. 603 | * @return ActiveField object 604 | * @throws InvalidConfigException 605 | * @see https://getbootstrap.com/docs/4.1/components/forms/#checkboxes-and-radios-1 606 | * - `unselect`: _string_, the value that should be submitted when none of the checkboxes is selected. By setting this 607 | * option, a hidden input will be generated. 608 | * - `separator`: _string_, the HTML code that separates items. 609 | * - `inline`: _boolean_, whether the list should be displayed as a series on the same line, default is false 610 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 611 | * single item in $items. The signature of this callback must be: 612 | * ~~~ 613 | * function ($index, $label, $name, $checked, $value) 614 | * ~~~ 615 | * 616 | * where `$index` is the zero-based index of the checkbox in the whole list; `$label` is the label for the checkbox; 617 | * and `$name`, `$value` and `$checked` represent the name, value and the checked status of the checkbox input. 618 | * 619 | */ 620 | public function checkboxList($items, $options = []) 621 | { 622 | return $this->getToggleFieldList(self::TYPE_CHECKBOX, $items, $options); 623 | } 624 | 625 | /** 626 | * @inheritdoc 627 | * @throws InvalidConfigException 628 | */ 629 | public function dropDownList($items, $options = []) 630 | { 631 | $this->initDisability($options); 632 | $newBsCss = ($this->form->isBs(5) ? 'form' : 'custom').'-select'; 633 | $class = $this->isCustomControl($options) ? $newBsCss : $this->addClass; 634 | Html::addCssClass($options, $class); 635 | 636 | return parent::dropDownList($items, $options); 637 | } 638 | 639 | /** 640 | * @inheritdoc 641 | */ 642 | public function hint($content, $options = []) 643 | { 644 | if ($this->getConfigParam('showHints') === false) { 645 | $this->parts['{hint}'] = ''; 646 | 647 | return $this; 648 | } 649 | if ($this->_isHintSpecial) { 650 | Html::addCssClass($options, 'kv-hint-block'); 651 | } 652 | 653 | return parent::hint($this->generateHint($content), $options); 654 | } 655 | 656 | /** 657 | * Checks whether bootstrap 4.x custom control based on `options` parameter 658 | * @param array $options HTML attributes for the control 659 | * @return bool 660 | * @throws InvalidConfigException|Exception 661 | */ 662 | protected function isCustomControl(&$options) 663 | { 664 | return ArrayHelper::remove($options, 'custom', false) && !$this->form->isBs(3); 665 | } 666 | 667 | /** 668 | * @inheritdoc 669 | * @throws InvalidConfigException 670 | */ 671 | public function fileInput($options = []) 672 | { 673 | $isBs5 = $this->form->isBs(5); 674 | if ($this->isCustomControl($options)) { 675 | if (!$isBs5) { 676 | $view = $this->form->getView(); 677 | Bs4CustomFileInputAsset::register($view); 678 | $view->registerJs('bsCustomFileInput.init();'); 679 | Html::removeCssClass($options, 'form-control'); 680 | Html::addCssClass($options, 'custom-file-input'); 681 | Html::addCssClass($this->labelOptions, 'custom-file-label'); 682 | $this->template = $this->customFileTemplate; 683 | } else { 684 | Html::addCssClass($options, 'form-control'); 685 | } 686 | } elseif ($isBs5) { 687 | Html::removeCssClass($options, 'form-control'); 688 | } 689 | 690 | return parent::fileInput($options); 691 | } 692 | 693 | /** 694 | * @inheritdoc 695 | * @throws InvalidConfigException 696 | */ 697 | public function input($type, $options = []) 698 | { 699 | $this->initFieldOptions($options); 700 | if ($this->isCustomControl($options) && $type === 'range') { 701 | Html::addCssClass($options, $this->getCustomCss('range')); 702 | } 703 | if ($type !== 'range' && $type !== 'color') { 704 | Html::addCssClass($options, $this->addClass); 705 | } 706 | $this->initDisability($options); 707 | 708 | return parent::input($type, $options); 709 | } 710 | 711 | /** 712 | * @inheritdoc 713 | */ 714 | public function label($label = null, $options = []) 715 | { 716 | $hasLabels = $this->hasLabels(); 717 | $processLabels = $label !== false && $this->_isHintSpecial && $hasLabels !== false && 718 | $hasLabels !== ActiveForm::SCREEN_READER && ($this->getHintData('onLabelClick') || $this->getHintData( 719 | 'onLabelHover' 720 | )); 721 | if ($processLabels) { 722 | if ($label === null) { 723 | $label = $this->model->getAttributeLabel($this->attribute); 724 | } 725 | $opts = ['class' => 'kv-type-label']; 726 | Html::addCssClass($opts, $this->getHintIconCss('Label')); 727 | $label = Html::tag('span', $label, $opts); 728 | if ($this->getHintData('showIcon') && !$this->getHintData('iconBesideInput')) { 729 | $label = Lib::strtr( 730 | $this->getHintData('labelTemplate'), 731 | ['{label}' => $label, '{help}' => $this->getHintIcon()] 732 | ); 733 | } 734 | } 735 | if (Lib::strpos($this->template, '{beginLabel}') !== false) { 736 | $this->renderLabelParts($label, $options); 737 | } 738 | if ($this->_offset) { 739 | $label = ''; 740 | } 741 | 742 | return parent::label($label, $options); 743 | } 744 | 745 | /** 746 | * @inheritdoc 747 | * @throws InvalidConfigException 748 | */ 749 | public function listBox($items, $options = []) 750 | { 751 | $this->initDisability($options); 752 | $newBsCss = $this->getCustomCss('select'); 753 | $class = $this->isCustomControl($options) ? $newBsCss : $this->addClass; 754 | Html::addCssClass($options, $class); 755 | 756 | return parent::listBox($items, $options); 757 | } 758 | 759 | /** 760 | * Gets custom CSS for custom controls supported in bootstrap 4.x and 5.x 761 | * @param string $type 762 | * @return string 763 | * @throws Exception 764 | */ 765 | protected function getCustomCss($type) 766 | { 767 | return $this->form->isBs(5) ? "form-{$type}" : "custom-{$type}"; 768 | } 769 | 770 | /** 771 | * @inheritdoc 772 | * @throws InvalidConfigException 773 | */ 774 | public function passwordInput($options = []) 775 | { 776 | $this->initFieldOptions($options); 777 | Html::addCssClass($options, $this->addClass); 778 | $this->initDisability($options); 779 | 780 | return parent::passwordInput($options); 781 | } 782 | 783 | /** 784 | * Renders a radio button. This method will generate the "checked" tag attribute according to the model attribute 785 | * value. 786 | * 787 | * @param array $options the tag options in terms of name-value pairs. The following options are specially 788 | * handled: 789 | * 790 | * - `custom`: _bool_, whether to render bootstrap 4.x custom radio styled control. Defaults to `false`. 791 | * This is applicable only for Bootstrap 4.x forms. 792 | * @param bool|null $enclosedByLabel whether to enclose the radio within the label. If `true`, the method will still 793 | * use [[template]] to layout the checkbox and the error message except that the radio is enclosed by the label tag. 794 | * 795 | * @return ActiveField object 796 | * @throws InvalidConfigException 797 | * @see https://getbootstrap.com/docs/4.1/components/forms/#checkboxes-and-radios-1 798 | * - `uncheck`: _string_, the value associated with the uncheck state of the radio button. If not set, it will take the 799 | * default value '0'. This method will render a hidden input so that if the radio button is not checked and is 800 | * submitted, the value of this attribute will still be submitted to the server via the hidden input. 801 | * - `label`: _string_, a label displayed next to the radio button. It will NOT be HTML-encoded. Therefore you can pass 802 | * in HTML code such as an image tag. If this is is coming from end users, you should [[Html::encode()]] it to 803 | * prevent XSS attacks. When this option is specified, the radio button will be enclosed by a label tag. 804 | * - `labelOptions`: _array_, the HTML attributes for the label tag. This is only used when the "label" option is 805 | * specified. 806 | * - `container: boolean|array, the HTML attributes for the checkbox container. If this is set to false, no 807 | * container will be rendered. The special option `tag` will be recognized which defaults to `div`. This 808 | * defaults to: `['tag' => 'div', 'class'=>'radio']` 809 | * The rest of the options will be rendered as the attributes of the resulting tag. The values will be HTML-encoded 810 | * using [[Html::encode()]]. If a value is null, the corresponding attribute will not be rendered. 811 | * 812 | */ 813 | public function radio($options = [], $enclosedByLabel = null) 814 | { 815 | return $this->getToggleField(self::TYPE_RADIO, $options, $enclosedByLabel); 816 | } 817 | 818 | /** 819 | * Renders a list of radio buttons. A radio button list is like a checkbox list, except that it only allows single 820 | * selection. The selection of the radio buttons is taken from the value of the model attribute. 821 | * 822 | * @param array $items the data item used to generate the radio buttons. The array keys are the labels, while the 823 | * array values are the corresponding radio button values. Note that the labels will NOT be HTML-encoded, while 824 | * the values will. 825 | * @param array $options options (name => config) for the radio button list. The following options are specially 826 | * handled: 827 | * 828 | * 829 | * - `custom`: _bool_, whether to render bootstrap 4.x custom checkbox/radio styled control. Defaults to `false`. 830 | * This is applicable only for Bootstrap 4.x forms. 831 | * @return ActiveField object 832 | * @throws InvalidConfigException 833 | * @see https://getbootstrap.com/docs/4.1/components/forms/#checkboxes-and-radios-1 834 | * - `unselect`: _string_, the value that should be submitted when none of the radio buttons is selected. By setting 835 | * this option, a hidden input will be generated. 836 | * - `separator`: _string_, the HTML code that separates items. 837 | * - `inline`: _boolean_, whether the list should be displayed as a series on the same line, default is false 838 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 839 | * single item in $items. The signature of this callback must be: 840 | * 841 | * ~~~ 842 | * function ($index, $label, $name, $checked, $value) 843 | * ~~~ 844 | * 845 | * where `$index` is the zero-based index of the radio button in the whole list; `$label` is the label for the radio 846 | * button; and `$name`, `$value` and `$checked` represent the name, value and the checked status of the radio button 847 | * input. 848 | * 849 | */ 850 | public function radioList($items, $options = []) 851 | { 852 | return $this->getToggleFieldList(self::TYPE_RADIO, $items, $options); 853 | } 854 | 855 | /** 856 | * @inheritdoc 857 | * @throws InvalidConfigException 858 | */ 859 | public function render($content = null) 860 | { 861 | if ($this->getConfigParam('showHints') === false) { 862 | $this->hintOptions['hint'] = ''; 863 | } else { 864 | if ($content === null && !isset($this->parts['{hint}']) && !isset($this->hintOptions['hint'])) { 865 | $this->hintOptions['hint'] = $this->generateHint(); 866 | } 867 | $this->template = Lib::strtr($this->template, ['{hint}' => $this->_settings['hint']]); 868 | } 869 | $staticOnly = $this->form->staticOnly; 870 | if (is_callable($staticOnly)) { 871 | $staticOnly = call_user_func($staticOnly, [$this->model, $this]); 872 | } 873 | if ($staticOnly === true) { 874 | $this->buildTemplate(); 875 | $this->staticInput(); 876 | } else { 877 | $this->initFieldOptions($this->inputOptions); 878 | $this->initDisability($this->inputOptions); 879 | $this->buildTemplate(); 880 | } 881 | 882 | return parent::render($content); 883 | } 884 | 885 | /** 886 | * @inheritdoc 887 | * @throws InvalidConfigException 888 | */ 889 | public function textInput($options = []) 890 | { 891 | $this->initFieldOptions($options); 892 | Html::addCssClass($options, $this->addClass); 893 | $this->initDisability($options); 894 | 895 | return parent::textInput($options); 896 | } 897 | 898 | /** 899 | * @inheritdoc 900 | * @throws InvalidConfigException 901 | */ 902 | public function textarea($options = []) 903 | { 904 | $this->initFieldOptions($options); 905 | Html::addCssClass($options, $this->addClass); 906 | $this->initDisability($options); 907 | 908 | return parent::textarea($options); 909 | } 910 | 911 | /** 912 | * @inheritdoc 913 | */ 914 | public function widget($class, $config = []) 915 | { 916 | if (property_exists($class, 'disabled') && property_exists($class, 'readonly')) { 917 | $this->initDisability($config); 918 | } 919 | 920 | return parent::widget($class, $config); 921 | } 922 | 923 | /** 924 | * Renders a static input (display only). 925 | * 926 | * @param array $options the tag options in terms of name-value pairs. 927 | * 928 | * @return ActiveField object 929 | * @throws Exception 930 | */ 931 | public function staticInput($options = []) 932 | { 933 | $content = isset($this->staticValue) ? $this->staticValue : Html::getAttributeValue($this->model, 934 | $this->attribute); 935 | $this->form->addCssClass($options, ActiveForm::BS_FORM_CONTROL_STATIC); 936 | $this->parts['{input}'] = Html::tag('div', $content, $options); 937 | $this->_isStatic = true; 938 | 939 | return $this; 940 | } 941 | 942 | /** 943 | * Renders a multi select list box. This control extends the checkboxList and radioList available in 944 | * [[YiiActiveField]] - to display a scrolling multi select list box. 945 | * 946 | * @param array $items the data item used to generate the checkboxes or radio. 947 | * @param array $options the options for checkboxList or radioList. Additional parameters 948 | * - `height`: _string_, the height of the multiselect control - defaults to 145px 949 | * - `selector`: _string_, whether checkbox or radio - defaults to checkbox 950 | * - `container`: _array_, options for the multiselect container 951 | * - `unselect`: _string_, the value that should be submitted when none of the radio buttons is selected. By setting 952 | * this option, a hidden input will be generated. 953 | * - `separator`: _string_, the HTML code that separates items. 954 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 955 | * single item in $items. The signature of this callback must be: 956 | * - `inline`: _boolean_, whether the list should be displayed as a series on the same line, default is false 957 | * - `selector`: _string_, whether the selection input is [[TYPE_RADIO]] or [[TYPE_CHECKBOX]] 958 | * 959 | * @return ActiveField object 960 | * @throws InvalidConfigException 961 | */ 962 | public function multiselect($items, $options = []) 963 | { 964 | $opts = $options; 965 | $this->initDisability($opts); 966 | $opts['encode'] = false; 967 | $height = ArrayHelper::remove($opts, 'height', self::MULTI_SELECT_HEIGHT); 968 | $selector = ArrayHelper::remove($opts, 'selector', self::TYPE_CHECKBOX); 969 | $container = ArrayHelper::remove($opts, 'container', []); 970 | Html::addCssStyle($container, 'height:'.$height, true); 971 | Html::addCssClass($container, $this->addClass.' input-multiselect'); 972 | $container['tabindex'] = 0; 973 | $this->_multiselect = Html::tag('div', '{input}', $container); 974 | 975 | return $selector == self::TYPE_RADIO ? $this->radioList($items, $opts) : $this->checkboxList($items, $opts); 976 | } 977 | 978 | /** 979 | * Renders a list of radio toggle buttons. 980 | * 981 | * @see http://getbootstrap.com/javascript/#buttons-checkbox-radio 982 | * 983 | * @param array $items the data item used to generate the radios. The array values are the labels, while the array 984 | * keys are the corresponding radio values. Note that the labels will NOT be HTML-encoded, while the values 985 | * will be encoded. 986 | * @param array $options options (name => config) for the radio button list. The following options are specially 987 | * handled: 988 | * 989 | * - `unselect`: _string_, the value that should be submitted when none of the radios is selected. By setting this 990 | * option, a hidden input will be generated. If you do not want any hidden input, you should explicitly set 991 | * this option as null. 992 | * - `separator`: _string_, the HTML code that separates items. 993 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 994 | * single item in $items. The signature of this callback must be: 995 | * 996 | * ~~~ 997 | * function ($index, $label, $name, $checked, $value) 998 | * ~~~ 999 | * 1000 | * where $index is the zero-based index of the radio button in the whole list; $label is the label for the radio 1001 | * button; and $name, $value and $checked represent the name, value and the checked status of the radio button 1002 | * input. 1003 | * 1004 | * @return ActiveField object 1005 | * @throws InvalidConfigException 1006 | */ 1007 | public function radioButtonGroup($items, $options = []) 1008 | { 1009 | return $this->getToggleFieldList(self::TYPE_RADIO, $items, $options, true); 1010 | } 1011 | 1012 | /** 1013 | * Renders a list of checkbox toggle buttons. 1014 | * 1015 | * @see http://getbootstrap.com/javascript/#buttons-checkbox-radio 1016 | * 1017 | * @param array $items the data item used to generate the checkboxes. The array values are the labels, while the 1018 | * array keys are the corresponding checkbox values. Note that the labels will NOT be HTML-encoded, while the 1019 | * values will. 1020 | * @param array $options options (name => config) for the checkbox button list. The following options are specially 1021 | * handled: 1022 | * 1023 | * - `unselect`: _string_, the value that should be submitted when none of the checkboxes is selected. By setting this 1024 | * option, a hidden input will be generated. If you do not want any hidden input, you should explicitly set 1025 | * this option as null. 1026 | * - `separator`: _string_, the HTML code that separates items. 1027 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 1028 | * single item in $items. The signature of this callback must be: 1029 | * 1030 | * ~~~ 1031 | * function ($index, $label, $name, $checked, $value) 1032 | * ~~~ 1033 | * 1034 | * where $index is the zero-based index of the checkbox button in the whole list; $label is the label for the 1035 | * checkbox button; and $name, $value and $checked represent the name, value and the checked status of the 1036 | * checkbox button input. 1037 | * 1038 | * @return ActiveField object 1039 | * @throws InvalidConfigException 1040 | */ 1041 | public function checkboxButtonGroup($items, $options = []) 1042 | { 1043 | return $this->getToggleFieldList(self::TYPE_CHECKBOX, $items, $options, true); 1044 | } 1045 | 1046 | /** 1047 | * Generates the hint icon 1048 | * 1049 | * @return string 1050 | * @throws Exception 1051 | */ 1052 | protected function getHintIcon() 1053 | { 1054 | if (!$this->getHintData('showIcon')) { 1055 | return ''; 1056 | } 1057 | $options = []; 1058 | Html::addCssClass($options, $this->getHintIconCss('Icon')); 1059 | 1060 | return Html::tag('span', $this->getHintData('icon'), $options); 1061 | } 1062 | 1063 | /** 1064 | * Gets bootstrap grid column CSS based on size 1065 | * @param string $size 1066 | * @return string 1067 | * @throws InvalidConfigException|Exception 1068 | */ 1069 | protected function getColCss($size) 1070 | { 1071 | $bsVer = $this->form->getBsVer(); 1072 | $sizes = ArrayHelper::getValue($this->form->bsColCssPrefixes, $bsVer, []); 1073 | if ($size == self::NOT_SET || !isset($sizes[$size])) { 1074 | return 'col-'.ActiveForm::SIZE_MEDIUM.'-'; 1075 | } 1076 | 1077 | return $sizes[$size]; 1078 | } 1079 | 1080 | /** 1081 | * Generates a toggle field (checkbox or radio) 1082 | * 1083 | * @param string $type the toggle input type 'checkbox' or 'radio'. 1084 | * @param array $options options (name => config) for the toggle input list container tag. 1085 | * @param bool|null $enclosedByLabel whether the input is enclosed by the label tag 1086 | * 1087 | * @return ActiveField object 1088 | * @throws InvalidConfigException 1089 | */ 1090 | protected function getToggleField($type = self::TYPE_CHECKBOX, $options = [], $enclosedByLabel = null) 1091 | { 1092 | $this->initDisability($options); 1093 | $bsVer = $this->form->getBsVer(); 1094 | $notBs3 = $bsVer !== 3; 1095 | $custom = $this->isCustomControl($options); 1096 | $switch = ArrayHelper::remove($options, 'switch', false) && $notBs3 && $type === self::TYPE_CHECKBOX; 1097 | if ($enclosedByLabel === null) { 1098 | $enclosedByLabel = !$notBs3 && !$custom; 1099 | } 1100 | if (!isset($options['template'])) { 1101 | $this->template = $enclosedByLabel ? $this->checkEnclosedTemplate : $this->checkTemplate; 1102 | } else { 1103 | $this->template = $options['template']; 1104 | unset($options['template']); 1105 | } 1106 | if ($bsVer === 4) { 1107 | $prefix = $custom ? 'custom-control' : 'form-check'; 1108 | } elseif ($bsVer === 5) { 1109 | $prefix = $custom ? 'form-check' : 'bs4 checkbox'; 1110 | } else { 1111 | $prefix = $type; 1112 | } 1113 | Html::removeCssClass($options, 'form-control'); 1114 | $this->form->removeCssClass($this->labelOptions, ActiveForm::BS_CONTROL_LABEL); 1115 | Html::addCssClass($this->checkWrapperOptions, $prefix); 1116 | if ($notBs3) { 1117 | Html::addCssClass($this->labelOptions, "{$prefix}-label"); 1118 | Html::addCssClass($options, "{$prefix}-input"); 1119 | if ($custom) { 1120 | $prefix = $this->form->isBs(5) ? 'form-' : 'custom-'; 1121 | Html::addCssClass($this->checkWrapperOptions, $prefix.($switch ? 'switch' : $type)); 1122 | } 1123 | } elseif (!$enclosedByLabel) { 1124 | Html::addCssClass($this->checkWrapperOptions, "not-enclosed"); 1125 | } 1126 | $this->template = Html::tag('div', $this->template, $this->checkWrapperOptions); 1127 | if ($this->form->isHorizontal()) { 1128 | Html::removeCssClass($this->labelOptions, $this->getColCss($this->deviceSize).$this->labelSpan); 1129 | if ($this->autoOffset) { 1130 | $this->template = Html::tag('div', '', ['class' => $this->_labelCss]). 1131 | Html::tag('div', $this->template, ['class' => $this->_inputCss]); 1132 | } else { 1133 | Html::removeCssClass($this->options, 'row'); 1134 | } 1135 | } 1136 | if ($this->form->isInline()) { 1137 | Html::removeCssClass($this->labelOptions, $this->form->getSrOnlyCss()); 1138 | } 1139 | if ($enclosedByLabel) { 1140 | if (isset($options['label'])) { 1141 | $this->parts['{labelTitle}'] = $options['label']; 1142 | } 1143 | $this->parts['{beginLabel}'] = Html::beginTag('label', $this->labelOptions); 1144 | $this->parts['{endLabel}'] = Html::endTag('label'); 1145 | } 1146 | 1147 | return parent::$type($options, false); 1148 | } 1149 | 1150 | /** 1151 | * Parses the form flag (for disabled/readonly). 1152 | * 1153 | * @param string $flag 1154 | * @param array $options 1155 | * @return void 1156 | */ 1157 | protected function parseFormFlag($flag, &$options) 1158 | { 1159 | if (!property_exists($this->form, $flag) || isset($options[$flag])) { 1160 | return; 1161 | } 1162 | $action = $this->form->$flag; 1163 | if ($action && is_callable($action)) { 1164 | $options[$flag] = call_user_func_array($action, [$this->model, $this]); 1165 | } else { 1166 | $options[$flag] = $action; 1167 | } 1168 | } 1169 | 1170 | /** 1171 | * Validates and sets disabled or readonly inputs 1172 | * 1173 | * @param array $options the HTML attributes for the input 1174 | */ 1175 | protected function initDisability(&$options) 1176 | { 1177 | $this->parseFormFlag('disabled', $options); 1178 | $this->parseFormFlag('readonly', $options); 1179 | } 1180 | 1181 | /** 1182 | * Gets configuration parameter from formConfig 1183 | * 1184 | * @param string $param the parameter name 1185 | * @param mixed $default the default parameter value 1186 | * 1187 | * @return string|bool the parsed parameter value 1188 | * @throws Exception 1189 | */ 1190 | protected function getConfigParam($param, $default = true) 1191 | { 1192 | return isset($this->$param) ? $this->$param : ArrayHelper::getValue($this->form->formConfig, $param, $default); 1193 | } 1194 | 1195 | /** 1196 | * Generates the hint. 1197 | * 1198 | * @param string $content the hint content 1199 | * 1200 | * @return string 1201 | */ 1202 | protected function generateHint($content = null) 1203 | { 1204 | if ($content === null && method_exists($this->model, 'getAttributeHint')) { 1205 | $content = $this->model->getAttributeHint($this->attribute); 1206 | } 1207 | 1208 | return $this->contentBeforeHint.$content.$this->contentAfterHint; 1209 | } 1210 | 1211 | /** 1212 | * Initialize the active field 1213 | * @throws InvalidConfigException 1214 | */ 1215 | protected function initActiveField() 1216 | { 1217 | if (isset($this->enableError)) { 1218 | $this->showErrors = $this->enableError; 1219 | } 1220 | if (isset($this->enableLabel)) { 1221 | $this->showLabels = $this->enableLabel; 1222 | } 1223 | $bsVer = $this->form->getBsVer(); 1224 | $isInline = $this->form->isInline(); 1225 | $isHorizontal = $this->form->isHorizontal(); 1226 | if ($bsVer > 3) { 1227 | $errCss = $this->form->tooltipStyleFeedback ? 'invalid-tooltip' : 'invalid-feedback'; 1228 | Html::addCssClass($this->errorOptions, $errCss); 1229 | } 1230 | $showLabels = $this->getConfigParam('showLabels'); 1231 | $this->_isHintSpecial = $this->hintType === self::HINT_SPECIAL; 1232 | if ($isInline && !isset($this->autoPlaceholder) && $showLabels !== true) { 1233 | $this->autoPlaceholder = true; 1234 | } elseif (!isset($this->autoPlaceholder)) { 1235 | $this->autoPlaceholder = false; 1236 | } 1237 | if (!isset($this->labelOptions['class']) && ($isHorizontal || $bsVer === 3 && !$isInline)) { 1238 | $this->labelOptions['class'] = $this->form->getCssClass(ActiveForm::BS_CONTROL_LABEL); 1239 | } 1240 | if ($showLabels === ActiveForm::SCREEN_READER) { 1241 | Html::addCssClass($this->labelOptions, $this->form->getSrOnlyCss()); 1242 | } 1243 | if ($this->showRequiredIndicator) { 1244 | Html::addCssClass($this->labelOptions, 'has-star'); 1245 | } 1246 | if ($this->highlightAddon) { 1247 | Html::addCssClass($this->options, 'highlight-addon'); 1248 | } 1249 | if ($isHorizontal) { 1250 | $this->initHorizontal(); 1251 | } 1252 | $this->initLabels(); 1253 | $this->initHints(); 1254 | $this->_hasFeedback = !empty($this->feedbackIcon) && is_array($this->feedbackIcon); 1255 | $this->_iconBesideInput = (bool)ArrayHelper::getValue($this->hintSettings, 'iconBesideInput'); 1256 | if ($this->_iconBesideInput) { 1257 | $id = ArrayHelper::getValue($this->options, 'id', ''); 1258 | $this->_hintPopoverContainer = $id ? "#{$id}-table" : ''; 1259 | } else { 1260 | $id = ArrayHelper::getValue($this->form->options, 'id', ''); 1261 | $this->_hintPopoverContainer = $id ? "#{$id}" : ''; 1262 | } 1263 | } 1264 | 1265 | /** 1266 | * Initialize label options 1267 | */ 1268 | protected function initLabels() 1269 | { 1270 | $labelCss = $this->_labelCss; 1271 | if ($this->hasLabels() === ActiveForm::SCREEN_READER) { 1272 | Html::addCssClass($this->labelOptions, $this->form->getSrOnlyCss()); 1273 | } elseif ($labelCss != self::NOT_SET) { 1274 | Html::addCssClass($this->labelOptions, $labelCss); 1275 | } 1276 | } 1277 | 1278 | /** 1279 | * Validate label display status 1280 | * 1281 | * @return boolean|string whether labels are to be shown 1282 | * @throws Exception 1283 | */ 1284 | protected function hasLabels() 1285 | { 1286 | $showLabels = $this->getConfigParam('showLabels'); // plus abfrage $this-showLabels kombinieren. 1287 | if ($this->autoPlaceholder && $showLabels !== ActiveForm::SCREEN_READER) { 1288 | $showLabels = false; 1289 | } 1290 | 1291 | return $showLabels; 1292 | } 1293 | 1294 | /** 1295 | * Prepares bootstrap grid col classes for horizontal layout including label and input tags and initiate private 1296 | * CSS variables. The process order for 'labelSpan' and 'wrapper' is as follows: 1297 | * 1298 | * - Step 1: Check `$labelSpan` and `$deviceSize`. 1299 | * - Step 2: Check `formConfig(['labelSpan' => x, 'deviceSize' => xy]) and build css tag. 1300 | * - If `horizontalCssClasses['wrapper']` is set and no 'col-' tag then add this to css tag from Step 1. 1301 | * - If `horizontalCssClasses['wrapper']` is set and wrapper has 'col-' tag then override css tag completely. 1302 | * - If no `$labelSpan` and no `horizontalCssClasses['wrapper']` is set then use default from [[$_settings]]. 1303 | * Similar behavior to `horizontalCssClasses['label']`. 1304 | * @throws InvalidConfigException 1305 | */ 1306 | protected function initHorizontal() 1307 | { 1308 | $hor = $this->horizontalCssClasses; 1309 | $span = $this->getConfigParam('labelSpan', ''); 1310 | $size = $this->getConfigParam('deviceSize', ''); 1311 | $bsVer = $this->form->getBsVer(); 1312 | if ($bsVer > 3) { 1313 | Html::addCssClass($this->options, 'row'); 1314 | } 1315 | // check horizontalCssClasses['wrapper'] if there is a col- class 1316 | if (isset($hor['wrapper']) && Lib::strpos($hor['wrapper'], 'col-') !== false) { 1317 | $span = ''; 1318 | } 1319 | if (empty($span) && !isset($hor['wrapper'])) { 1320 | $span = $this->_settings['labelSpan']; 1321 | } 1322 | if (empty($size)) { 1323 | $size = ArrayHelper::getValue($this->_settings, 'deviceSize'); 1324 | } 1325 | $this->deviceSize = $size; 1326 | if (empty($span)) { 1327 | $span = ActiveForm::DEFAULT_LABEL_SPAN; 1328 | } 1329 | if ($span != self::NOT_SET && intval($span) > 0) { 1330 | $span = intval($span); 1331 | 1332 | // validate if invalid labelSpan is passed - else set to DEFAULT_LABEL_SPAN 1333 | if ($span <= 0 || $span >= $this->form->fullSpan) { 1334 | $span = $this->form->fullSpan; 1335 | } 1336 | 1337 | // validate if invalid deviceSize is passed - else default to SIZE_MEDIUM 1338 | $sizes = [ActiveForm::SIZE_TINY, ActiveForm::SIZE_SMALL, ActiveForm::SIZE_MEDIUM, ActiveForm::SIZE_LARGE]; 1339 | if ($size == self::NOT_SET || !in_array($size, $sizes)) { 1340 | $size = ActiveForm::SIZE_MEDIUM; 1341 | } 1342 | 1343 | $this->labelSpan = $span; 1344 | $prefix = $this->getColCss($size); 1345 | $this->_labelCss = $prefix.$span; 1346 | $this->_inputCss = $prefix.($this->form->fullSpan - $span); 1347 | } 1348 | 1349 | if (isset($hor['wrapper'])) { 1350 | if ($span !== self::NOT_SET) { 1351 | $this->_inputCss .= " "; 1352 | } 1353 | $this->_inputCss .= implode(' ', (array)$hor['wrapper']); 1354 | } 1355 | 1356 | if (isset($hor['label'])) { 1357 | if ($span !== self::NOT_SET) { 1358 | $this->_labelCss .= " "; 1359 | } 1360 | $this->_labelCss .= implode(' ', (array)$hor['label']); 1361 | } 1362 | 1363 | if (isset($hor['error'])) { 1364 | Html::addCssClass($this->errorOptions, $hor['error']); 1365 | } 1366 | } 1367 | 1368 | /** 1369 | * Initialize layout settings for label, input, error and hint blocks and for various bootstrap 3 form layouts 1370 | * @throws InvalidConfigException 1371 | */ 1372 | protected function initLayout() 1373 | { 1374 | $showLabels = $this->hasLabels(); 1375 | $showErrors = $this->getConfigParam('showErrors'); 1376 | $this->mergeSettings($showLabels, $showErrors); 1377 | $this->buildLayoutParts($showLabels, $showErrors); 1378 | } 1379 | 1380 | /** 1381 | * Merges the parameters for layout settings 1382 | * 1383 | * @param boolean $showLabels whether to show labels 1384 | * @param boolean $showErrors whether to show errors 1385 | */ 1386 | protected function mergeSettings($showLabels, $showErrors) 1387 | { 1388 | $this->_settings['showLabels'] = $showLabels; 1389 | $this->_settings['showErrors'] = $showErrors; 1390 | } 1391 | 1392 | /** 1393 | * Builds the field layout parts 1394 | * 1395 | * @param boolean $showLabels whether to show labels 1396 | * @param boolean $showErrors whether to show errors 1397 | * @throws InvalidConfigException 1398 | */ 1399 | protected function buildLayoutParts($showLabels, $showErrors) 1400 | { 1401 | if (!$showErrors) { 1402 | $this->_settings['error'] = ''; 1403 | } 1404 | if ($this->skipFormLayout) { 1405 | $this->mergeSettings($showLabels, $showErrors); 1406 | $this->parts['{beginWrapper}'] = ''; 1407 | $this->parts['{endWrapper}'] = ''; 1408 | $this->parts['{beginLabel}'] = ''; 1409 | $this->parts['{labelTitle}'] = ''; 1410 | $this->parts['{endLabel}'] = ''; 1411 | 1412 | return; 1413 | } 1414 | if (!empty($this->_inputCss)) { 1415 | $inputDivClass = $this->_inputCss; 1416 | if ($showLabels === false || $showLabels === ActiveForm::SCREEN_READER) { 1417 | $inputDivClass = $this->getColCss($this->deviceSize).$this->form->fullSpan; 1418 | } 1419 | Html::addCssClass($this->wrapperOptions, $inputDivClass); 1420 | } 1421 | if (!isset($this->parts['{beginWrapper}'])) { 1422 | if ($this->renderEmptyWrapper || !empty($this->wrapperOptions)) { 1423 | $options = $this->wrapperOptions; 1424 | $tag = ArrayHelper::remove($options, 'tag', 'div'); 1425 | $this->parts['{beginWrapper}'] = Html::beginTag($tag, $options); 1426 | $this->parts['{endWrapper}'] = Html::endTag($tag); 1427 | } else { 1428 | $this->parts['{beginWrapper}'] = $this->parts['{endWrapper}'] = ''; 1429 | } 1430 | } 1431 | $this->mergeSettings($showLabels, $showErrors); 1432 | } 1433 | 1434 | /** 1435 | * Sets the layout element container 1436 | * 1437 | * @param string $type the layout element type 1438 | * @param string $css the css class for the container 1439 | * @param boolean $chk whether to create the container for the layout element 1440 | */ 1441 | protected function setLayoutContainer($type, $css = '', $chk = true) 1442 | { 1443 | if (!empty($css) && $chk) { 1444 | $this->_settings[$type] = "
{{$type}}
"; 1445 | } 1446 | } 1447 | 1448 | /** 1449 | * Initialize hint settings 1450 | * @throws InvalidConfigException|Exception 1451 | */ 1452 | protected function initHints() 1453 | { 1454 | if ($this->hintType !== self::HINT_SPECIAL) { 1455 | return; 1456 | } 1457 | $container = $this->_hintPopoverContainer; 1458 | if ($container === '') { 1459 | $container = $this->_iconBesideInput ? 'table' : 'form'; 1460 | } 1461 | $iconCss = !$this->form->isBs(3) ? 'fas fa fa-question-circle' : 'glyphicon glyphicon-question-sign'; 1462 | $attr = 'style="width:100%"{id}'; 1463 | $defaultSettings = [ 1464 | 'showIcon' => true, 1465 | 'iconBesideInput' => false, 1466 | 'labelTemplate' => '{label}{help}', 1467 | 'inputTemplate' => "".'
{input}{help}
', 1468 | 'onLabelClick' => false, 1469 | 'onLabelHover' => true, 1470 | 'onIconClick' => true, 1471 | 'onIconHover' => false, 1472 | 'labelCssClass' => 'kv-hint-label', 1473 | 'iconCssClass' => 'kv-hint-icon', 1474 | 'contentCssClass' => 'kv-hint-content', 1475 | 'icon' => '', 1476 | 'hideOnEscape' => true, 1477 | 'hideOnClickOut' => true, 1478 | 'placement' => 'top', 1479 | 'container' => $container, 1480 | 'viewport' => ['selector' => 'body', 'padding' => 0], 1481 | ]; 1482 | $this->hintSettings = array_replace_recursive($defaultSettings, $this->hintSettings); 1483 | Html::addCssClass($this->options, 'kv-hint-special'); 1484 | foreach (static::$_pluginHintKeys as $key) { 1485 | $this->setHintData($key); 1486 | } 1487 | } 1488 | 1489 | /** 1490 | * Sets a hint property setting as a data attribute within `self::$options` 1491 | * 1492 | * @param string $key the hint property key 1493 | */ 1494 | protected function setHintData($key) 1495 | { 1496 | if (isset($this->hintSettings[$key])) { 1497 | $value = $this->hintSettings[$key]; 1498 | $this->options['data-'.Inflector::camel2id($key)] = is_bool($value) ? (int)$value : $value; 1499 | } 1500 | } 1501 | 1502 | /** 1503 | * Initializes sizes and placeholder based on $autoPlaceholder 1504 | * 1505 | * @param array $options the HTML attributes for the input 1506 | * @throws InvalidConfigException 1507 | */ 1508 | protected function initFieldOptions(&$options) 1509 | { 1510 | $this->initFieldSize($options, 'lg'); 1511 | $this->initFieldSize($options, 'sm'); 1512 | if ($this->autoPlaceholder) { 1513 | $label = $this->model->getAttributeLabel(Html::getAttributeName($this->attribute)); 1514 | $this->inputOptions['placeholder'] = $label; 1515 | $options['placeholder'] = $label; 1516 | } 1517 | $this->addErrorClassBS4($options); 1518 | } 1519 | 1520 | /** 1521 | * Initializes field by detecting the bootstrap CSS size and sets a size modifier CSS to the field container 1522 | * @param array $options the HTML options 1523 | * @param string $size the size to init 1524 | * @throws InvalidConfigException|Exception 1525 | */ 1526 | protected function initFieldSize($options, $size) 1527 | { 1528 | $notBs3 = !$this->form->isBs(3); 1529 | if ($notBs3 && Config::hasCssClass($options, "form-control-{$size}") || 1530 | !$notBs3 && Config::hasCssClass($options, "input-{$size}") || 1531 | isset($this->addon['groupOptions']) && 1532 | Config::hasCssClass($this->addon['groupOptions'], "input-group-{$size}")) { 1533 | Html::addCssClass($this->options, "has-size-{$size}"); 1534 | } 1535 | } 1536 | 1537 | /** 1538 | * Gets a hint configuration setting value 1539 | * 1540 | * @param string $key the hint setting key to fetch 1541 | * @param mixed $default the default value if not set 1542 | * 1543 | * @return mixed 1544 | * @throws Exception 1545 | */ 1546 | protected function getHintData($key, $default = null) 1547 | { 1548 | return ArrayHelper::getValue($this->hintSettings, $key, $default); 1549 | } 1550 | 1551 | /** 1552 | * Gets the hint icon css based on `hintSettings` 1553 | * 1554 | * @param string $type whether `Label` or `Icon` 1555 | * 1556 | * @return array the css to be applied 1557 | */ 1558 | protected function getHintIconCss($type) 1559 | { 1560 | $css = ["kv-hintable"]; 1561 | if ($type === 'Icon') { 1562 | $css[] = 'hide'; 1563 | } 1564 | if (!empty($this->hintSettings["on{$type}Click"])) { 1565 | $css[] = "kv-hint-click"; 1566 | } 1567 | if (!empty($this->hintSettings["on{$type}Hover"])) { 1568 | $css[] = "kv-hint-hover"; 1569 | } 1570 | 1571 | return $css; 1572 | } 1573 | 1574 | /** 1575 | * Builds the final template based on the bootstrap form type, display settings for label, error, and hint, and 1576 | * content before and after label, input, error, and hint. 1577 | * @throws InvalidConfigException 1578 | */ 1579 | protected function buildTemplate() 1580 | { 1581 | $showLabels = $showErrors = $input = $error = null; 1582 | extract($this->_settings); 1583 | if ($this->_isStatic || (isset($this->showErrors) && !$this->showErrors) || 1584 | (!$this->skipFormLayout && !$this->getConfigParam('showErrors'))) { 1585 | $showErrors = false; 1586 | } 1587 | $showLabels = $showLabels && $this->hasLabels(); 1588 | $this->buildLayoutParts($showLabels, $showErrors); 1589 | extract($this->_settings); 1590 | if (!$showErrors) { 1591 | Html::addCssClass($this->options, 'hide-errors'); 1592 | } 1593 | if (!empty($this->_multiselect)) { 1594 | $input = Lib::str_replace('{input}', $this->_multiselect, $input); 1595 | } 1596 | if ($this->_isHintSpecial && $this->getHintData('iconBesideInput') && $this->getHintData('showIcon')) { 1597 | $id = $this->_hintPopoverContainer ? ' id="'.$this->_hintPopoverContainer.'"' : ''; 1598 | $help = Lib::strtr($this->getHintData('inputTemplate'), ['{help}' => $this->getHintIcon(), '{id}' => $id,]); 1599 | $input = Lib::str_replace('{input}', $help, $input); 1600 | } 1601 | $newInput = $this->contentBeforeInput.$this->generateAddon().$this->renderFeedbackIcon(). 1602 | $this->contentAfterInput; 1603 | $newError = "{$this->contentBeforeError}{error}{$this->contentAfterError}"; 1604 | $config = [ 1605 | '{beginLabel}' => $showLabels ? '{beginLabel}' : "", 1606 | '{endLabel}' => $showLabels ? '{endLabel}' : "", 1607 | '{label}' => $showLabels ? "{$this->contentBeforeLabel}{label}{$this->contentAfterLabel}" : "", 1608 | '{labelTitle}' => $showLabels ? "{$this->contentBeforeLabel}{labelTitle}{$this->contentAfterLabel}" : "", 1609 | '{input}' => Lib::str_replace('{input}', $newInput, $input), 1610 | '{error}' => $showErrors ? Lib::str_replace('{error}', $newError, $error) : '', 1611 | ]; 1612 | $this->template = Lib::strtr($this->template, $config); 1613 | } 1614 | 1615 | /** 1616 | * Generates the addon markup 1617 | * 1618 | * @return string 1619 | * @throws InvalidConfigException|Exception 1620 | */ 1621 | protected function generateAddon() 1622 | { 1623 | if (empty($this->addon)) { 1624 | return '{input}'; 1625 | } 1626 | $addon = $this->addon; 1627 | $ver = $this->form->getBsVer(); 1628 | $prepend = $this->getAddonContent('prepend', $ver); 1629 | $append = $this->getAddonContent('append', $ver); 1630 | $content = $prepend.'{input}'.$append; 1631 | $group = ArrayHelper::getValue($addon, 'groupOptions', []); 1632 | Html::addCssClass($group, 'input-group'); 1633 | $contentBefore = ArrayHelper::getValue($addon, 'contentBefore', ''); 1634 | $contentAfter = ArrayHelper::getValue($addon, 'contentAfter', ''); 1635 | 1636 | return Html::tag('div', $contentBefore.$content.$contentAfter, $group); 1637 | } 1638 | 1639 | /** 1640 | * Render the bootstrap feedback icon 1641 | * 1642 | * @see http://getbootstrap.com/css/#with-optional-icons 1643 | * 1644 | * @return string 1645 | * @throws Exception 1646 | */ 1647 | protected function renderFeedbackIcon() 1648 | { 1649 | if (!$this->_hasFeedback) { 1650 | return ''; 1651 | } 1652 | $config = $this->feedbackIcon; 1653 | $type = ArrayHelper::getValue($config, 'type', 'icon'); 1654 | $prefix = ArrayHelper::getValue($config, 'prefix', $this->form->getDefaultIconPrefix()); 1655 | $id = Html::getInputId($this->model, $this->attribute); 1656 | 1657 | return $this->getFeedbackIcon($config, 'default', $type, $prefix, $id). 1658 | $this->getFeedbackIcon($config, 'success', $type, $prefix, $id). 1659 | $this->getFeedbackIcon($config, 'error', $type, $prefix, $id); 1660 | } 1661 | 1662 | /** 1663 | * Render the label parts 1664 | * 1665 | * @param string|null $label the label or null to use model label 1666 | * @param array $options the tag options 1667 | */ 1668 | protected function renderLabelParts($label = null, $options = []) 1669 | { 1670 | $options = array_merge($this->labelOptions, $options); 1671 | if ($label === null) { 1672 | if (isset($options['label'])) { 1673 | $label = $options['label']; 1674 | unset($options['label']); 1675 | } else { 1676 | $attribute = Html::getAttributeName($this->attribute); 1677 | $label = Html::encode($this->model->getAttributeLabel($attribute)); 1678 | } 1679 | } 1680 | if (!isset($options['for'])) { 1681 | $options['for'] = Html::getInputId($this->model, $this->attribute); 1682 | } 1683 | $this->parts['{beginLabel}'] = Html::beginTag('label', $options); 1684 | $this->parts['{endLabel}'] = Html::endTag('label'); 1685 | if (!isset($this->parts['{labelTitle}'])) { 1686 | $this->parts['{labelTitle}'] = $label; 1687 | } 1688 | } 1689 | 1690 | /** 1691 | * Generates a feedback icon 1692 | * 1693 | * @param array $config the feedback icon configuration 1694 | * @param string $cat the feedback icon category 1695 | * @param string $type the feedback icon type 1696 | * @param string $prefix the feedback icon prefix 1697 | * @param string $id the input attribute identifier 1698 | * 1699 | * @return string 1700 | * @throws Exception 1701 | */ 1702 | protected function getFeedbackIcon($config, $cat, $type, $prefix, $id) 1703 | { 1704 | $markup = ArrayHelper::getValue($config, $cat); 1705 | if ($markup === null) { 1706 | return ''; 1707 | } 1708 | $desc = ArrayHelper::remove($options, 'description', "({$cat})"); 1709 | $options = ArrayHelper::getValue($config, $cat.'Options', []); 1710 | $options['aria-hidden'] = true; 1711 | $key = $id.'-'.$cat; 1712 | $this->inputOptions['aria-describedby'] = empty($this->inputOptions['aria-describedby']) ? $key : 1713 | $this->inputOptions['aria-describedby'].' '.$key; 1714 | Html::addCssClass($options, ['form-control-feedback', "kv-feedback-{$cat}"]); 1715 | $icon = $type === 'raw' ? $markup : Html::tag('i', '', ['class' => $prefix.$markup]); 1716 | 1717 | return Html::tag('span', $icon, $options). 1718 | Html::tag('span', $desc, ['id' => $key, 'class' => $this->form->getSrOnlyCss()]); 1719 | } 1720 | 1721 | /** 1722 | * Renders a list of checkboxes / radio buttons. The selection of the checkbox / radio buttons is taken from the 1723 | * value of the model attribute. 1724 | * 1725 | * @param string $type the toggle input type 'checkbox' or 'radio'. 1726 | * @param array $items the data item used to generate the checkbox / radio buttons. The array keys are the labels, 1727 | * while the array values are the corresponding checkbox / radio button values. Note that the labels will NOT 1728 | * be HTML-encoded, while the values will be encoded. 1729 | * @param array $options options (name => config) for the checkbox / radio button list. The following options are 1730 | * specially handled: 1731 | * 1732 | * - `custom`: _bool_, whether to render bootstrap 4.x custom checkbox/radio styled control. Defaults to `false`. 1733 | * This is applicable only for Bootstrap 4.x forms. 1734 | * @param boolean $asBtnGrp whether to generate the toggle list as a bootstrap button group 1735 | * 1736 | * @return ActiveField object 1737 | * @throws InvalidConfigException 1738 | * @see https://getbootstrap.com/docs/4.1/components/forms/#checkboxes-and-radios-1 1739 | * - `unselect`: _string_, the value that should be submitted when none of the checkbox / radio buttons is selected. By 1740 | * setting this option, a hidden input will be generated. 1741 | * - `separator`: _string_, the HTML code that separates items. 1742 | * - `inline`: _boolean_, whether the list should be displayed as a series on the same line, default is false 1743 | * - `disabledItems`: _array_, the list of values that will be disabled. 1744 | * - `readonlyItems`: _array_, the list of values that will be readonly. 1745 | * - `item: callable, a callback that can be used to customize the generation of the HTML code corresponding to a 1746 | * single item in $items. The signature of this callback must be: 1747 | * 1748 | * ~~~ 1749 | * function ($index, $label, $name, $checked, $value) 1750 | * ~~~ 1751 | * 1752 | * where $index is the zero-based index of the checkbox/ radio button in the whole list; $label is the label for 1753 | * the checkbox/ radio button; and $name, $value and $checked represent the name, value and the checked status 1754 | * of the checkbox/ radio button input. 1755 | * 1756 | */ 1757 | protected function getToggleFieldList($type, $items, $options = [], $asBtnGrp = false) 1758 | { 1759 | $isBs5 = $this->form->isBs(5); 1760 | $notBs3 = !$this->form->isBs(3); 1761 | $disabled = ArrayHelper::remove($options, 'disabledItems', []); 1762 | $readonly = ArrayHelper::remove($options, 'readonlyItems', []); 1763 | $cust = $this->isCustomControl($options); 1764 | if ($isBs5) { 1765 | $pre = ($cust ? '' : ' bs5-form-check ').'form-check'; 1766 | } else { 1767 | $pre = $cust ? 'custom-control' : 'form-check'; 1768 | } 1769 | 1770 | if ($asBtnGrp) { 1771 | $css = ['btn-group']; 1772 | if (!$isBs5) { 1773 | $css[] = 'btn-group-toggle'; 1774 | } 1775 | Html::addCssClass($options, $css); 1776 | $options['data-toggle'] = 'buttons'; 1777 | $options['inline'] = true; 1778 | if (!isset($options['itemOptions']['labelOptions']['class'])) { 1779 | $options['itemOptions']['labelOptions']['class'] = 'btn '.$this->form->getDefaultBtnCss(); 1780 | } 1781 | } 1782 | $in = ArrayHelper::remove($options, 'inline', false); 1783 | $inputType = "{$type}List"; 1784 | $opts = ArrayHelper::getValue($options, 'itemOptions', []); 1785 | $this->initDisability($opts); 1786 | $css = !empty($opts['disabled']) ? ' disabled' : ''; 1787 | $css .= !empty($opts['readonly']) ? ' readonly' : ''; 1788 | if ($notBs3) { 1789 | Html::addCssClass($this->labelOptions, 'pt-0'); 1790 | } 1791 | if (!$notBs3 && $in && !isset($options['itemOptions']['labelOptions']['class'])) { 1792 | $options['itemOptions']['labelOptions']['class'] = "{$type}-inline{$css}"; 1793 | } elseif (!isset($options['item'])) { 1794 | $labelOpts = ArrayHelper::getValue($opts, 'labelOptions', []); 1795 | $options['item'] = function ($index, $label, $name, $checked, $value) 1796 | use ( 1797 | $isBs5, 1798 | $type, 1799 | $css, 1800 | $disabled, 1801 | $readonly, 1802 | $asBtnGrp, 1803 | $labelOpts, 1804 | $opts, 1805 | $in, 1806 | $notBs3, 1807 | $cust, 1808 | $pre, 1809 | $options 1810 | ) { 1811 | $id = isset($options['id']) ? $options['id'].'-'.$index : 1812 | Lib::strtolower(Lib::preg_replace('/[^a-zA-Z0-9=\s—–-]+/u', '-', $name)).'-'.$index; 1813 | $opts += [ 1814 | 'data-index' => $index, 1815 | 'value' => $value 1816 | ]; 1817 | $enclosedLabel = (!$cust && !$notBs3) || ($asBtnGrp && !$isBs5); 1818 | if ($enclosedLabel) { 1819 | $opts += ['label' => $label]; 1820 | } 1821 | if (!isset($opts['id'])) { 1822 | $opts['id'] = $id; 1823 | } 1824 | $wrapperOptions = []; 1825 | if ($notBs3 && !$asBtnGrp) { 1826 | $opts += ['class' => "{$pre}-input"]; 1827 | Html::addCssClass($labelOpts, "{$pre}-label"); 1828 | $wrapperOptions = ['class' => [$pre.($cust ? ' custom-'.$type : '')]]; 1829 | if ($in) { 1830 | Html::addCssClass($wrapperOptions, "{$pre}-inline"); 1831 | } 1832 | } elseif (!$notBs3) { 1833 | $wrapperOptions = ['class' => [$type.$css]]; 1834 | } 1835 | if ($asBtnGrp) { 1836 | if ($checked) { 1837 | Html::addCssClass($labelOpts, 'active'); 1838 | } 1839 | $opts['autocomplete'] = 'off'; 1840 | } 1841 | if (!empty($disabled) && in_array($value, $disabled) || !empty($opts['disabled'])) { 1842 | Html::addCssClass($labelOpts, 'disabled'); 1843 | } 1844 | if (!empty($readonly) && in_array($value, $readonly) || !empty($opts['readonly'])) { 1845 | Html::addCssClass($labelOpts, 'readonly'); 1846 | } 1847 | if ($isBs5 && $asBtnGrp) { 1848 | Html::addCssClass($opts, 'btn-check'); 1849 | } else { 1850 | $opts['labelOptions'] = $labelOpts; 1851 | } 1852 | $out = Html::$type($name, $checked, $opts); 1853 | if (!$enclosedLabel) { 1854 | $out .= Html::label($label, $opts['id'], $labelOpts); 1855 | } 1856 | 1857 | return $asBtnGrp ? $out : Html::tag('div', $out, $wrapperOptions); 1858 | }; 1859 | } 1860 | 1861 | return parent::$inputType($items, $options); 1862 | } 1863 | 1864 | /** 1865 | * Adds Bootstrap 4 validation class to the input options if needed. 1866 | * @param array $options 1867 | * @throws Exception 1868 | */ 1869 | protected function addErrorClassBS4(&$options) 1870 | { 1871 | $attributeName = Html::getAttributeName($this->attribute); 1872 | if (!$this->form->isBs(3) && 1873 | $this->model->hasErrors($attributeName) && 1874 | $this->form->validationStateOn === ActiveForm::VALIDATION_STATE_ON_CONTAINER) { 1875 | Html::addCssClass($options, 'is-invalid'); 1876 | } 1877 | } 1878 | } 1879 | --------------------------------------------------------------------------------