MaskedInput.php 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. <?php
  2. /**
  3. * @link http://www.yiiframework.com/
  4. * @copyright Copyright (c) 2008 Yii Software LLC
  5. * @license http://www.yiiframework.com/license/
  6. */
  7. namespace yii\widgets;
  8. use yii\base\InvalidConfigException;
  9. use yii\helpers\Html;
  10. use yii\helpers\Json;
  11. use yii\web\JsExpression;
  12. use yii\web\View;
  13. /**
  14. * MaskedInput generates a masked text input.
  15. *
  16. * MaskedInput is similar to [[Html::textInput()]] except that an input mask will be used to force users to enter
  17. * properly formatted data, such as phone numbers, social security numbers.
  18. *
  19. * To use MaskedInput, you must set the [[mask]] property. The following example
  20. * shows how to use MaskedInput to collect phone numbers:
  21. *
  22. * ```php
  23. * echo MaskedInput::widget([
  24. * 'name' => 'phone',
  25. * 'mask' => '999-999-9999',
  26. * ]);
  27. * ```
  28. *
  29. * You can also use this widget in an [[ActiveForm]] using the [[ActiveField::widget()|widget()]]
  30. * method, for example like this:
  31. *
  32. * ```php
  33. * <?= $form->field($model, 'from_date')->widget(\yii\widgets\MaskedInput::className(), [
  34. * 'mask' => '999-999-9999',
  35. * ]) ?>
  36. * ```
  37. *
  38. * The masked text field is implemented based on the
  39. * [jQuery input masked plugin](https://github.com/RobinHerbots/Inputmask).
  40. *
  41. * @author Kartik Visweswaran <kartikv2@gmail.com>
  42. * @since 2.0
  43. */
  44. class MaskedInput extends InputWidget
  45. {
  46. /**
  47. * The name of the jQuery plugin to use for this widget.
  48. */
  49. const PLUGIN_NAME = 'inputmask';
  50. /**
  51. * @var string|array|JsExpression the input mask (e.g. '99/99/9999' for date input). The following characters
  52. * can be used in the mask and are predefined:
  53. *
  54. * - `a`: represents an alpha character (A-Z, a-z)
  55. * - `9`: represents a numeric character (0-9)
  56. * - `*`: represents an alphanumeric character (A-Z, a-z, 0-9)
  57. * - `[` and `]`: anything entered between the square brackets is considered optional user input. This is
  58. * based on the `optionalmarker` setting in [[clientOptions]].
  59. *
  60. * Additional definitions can be set through the [[definitions]] property.
  61. */
  62. public $mask;
  63. /**
  64. * @var array custom mask definitions to use. Should be configured as `maskSymbol => settings`, where
  65. *
  66. * - `maskSymbol` is a string, containing a character to identify your mask definition and
  67. * - `settings` is an array, consisting of the following entries:
  68. * - `validator`: string, a JS regular expression or a JS function.
  69. * - `cardinality`: int, specifies how many characters are represented and validated for the definition.
  70. * - `prevalidator`: array, validate the characters before the definition cardinality is reached.
  71. * - `definitionSymbol`: string, allows shifting values from other definitions, with this `definitionSymbol`.
  72. */
  73. public $definitions;
  74. /**
  75. * @var array custom aliases to use. Should be configured as `maskAlias => settings`, where
  76. *
  77. * - `maskAlias` is a string containing a text to identify your mask alias definition (e.g. 'phone') and
  78. * - `settings` is an array containing settings for the mask symbol, exactly similar to parameters as passed in [[clientOptions]].
  79. */
  80. public $aliases;
  81. /**
  82. * @var array the JQuery plugin options for the input mask plugin.
  83. * @see https://github.com/RobinHerbots/Inputmask
  84. */
  85. public $clientOptions = [];
  86. /**
  87. * @var array the HTML attributes for the input tag.
  88. * @see \yii\helpers\Html::renderTagAttributes() for details on how attributes are being rendered.
  89. */
  90. public $options = ['class' => 'form-control'];
  91. /**
  92. * @var string the type of the input tag. Currently only 'text' and 'tel' are supported.
  93. * @see https://github.com/RobinHerbots/Inputmask
  94. * @since 2.0.6
  95. */
  96. public $type = 'text';
  97. /**
  98. * @var string the hashed variable to store the pluginOptions
  99. */
  100. protected $_hashVar;
  101. /**
  102. * Initializes the widget.
  103. *
  104. * @throws InvalidConfigException if the "mask" property is not set.
  105. */
  106. public function init()
  107. {
  108. parent::init();
  109. if (empty($this->mask) && empty($this->clientOptions['alias'])) {
  110. throw new InvalidConfigException("Either the 'mask' property or the 'clientOptions[\"alias\"]' property must be set.");
  111. }
  112. }
  113. /**
  114. * {@inheritdoc}
  115. */
  116. public function run()
  117. {
  118. $this->registerClientScript();
  119. echo $this->renderInputHtml($this->type);
  120. }
  121. /**
  122. * Generates a hashed variable to store the plugin `clientOptions`.
  123. *
  124. * Helps in reusing the variable for similar
  125. * options passed for other widgets on the same page. The following special data attribute will also be
  126. * added to the input field to allow accessing the client options via javascript:
  127. *
  128. * - 'data-plugin-inputmask' will store the hashed variable storing the plugin options.
  129. *
  130. * @param View $view the view instance
  131. * @author [Thiago Talma](https://github.com/thiagotalma)
  132. */
  133. protected function hashPluginOptions($view)
  134. {
  135. $encOptions = empty($this->clientOptions) ? '{}' : Json::htmlEncode($this->clientOptions);
  136. $this->_hashVar = self::PLUGIN_NAME . '_' . hash('crc32', $encOptions);
  137. $this->options['data-plugin-' . self::PLUGIN_NAME] = $this->_hashVar;
  138. $view->registerJs("var {$this->_hashVar} = {$encOptions};", View::POS_HEAD);
  139. }
  140. /**
  141. * Initializes client options.
  142. */
  143. protected function initClientOptions()
  144. {
  145. $options = $this->clientOptions;
  146. foreach ($options as $key => $value) {
  147. if (
  148. !$value instanceof JsExpression
  149. && in_array($key, [
  150. 'oncomplete', 'onincomplete', 'oncleared', 'onKeyUp', 'onKeyDown', 'onBeforeMask',
  151. 'onBeforePaste', 'onUnMask', 'isComplete', 'determineActiveMasksetIndex',
  152. ], true)
  153. ) {
  154. $options[$key] = new JsExpression($value);
  155. }
  156. }
  157. $this->clientOptions = $options;
  158. }
  159. /**
  160. * Registers the needed client script and options.
  161. */
  162. public function registerClientScript()
  163. {
  164. $js = '';
  165. $view = $this->getView();
  166. $this->initClientOptions();
  167. if (!empty($this->mask)) {
  168. $this->clientOptions['mask'] = $this->mask;
  169. }
  170. $this->hashPluginOptions($view);
  171. if (is_array($this->definitions) && !empty($this->definitions)) {
  172. $js .= ucfirst(self::PLUGIN_NAME) . '.extendDefinitions(' . Json::htmlEncode($this->definitions) . ');';
  173. }
  174. if (is_array($this->aliases) && !empty($this->aliases)) {
  175. $js .= ucfirst(self::PLUGIN_NAME) . '.extendAliases(' . Json::htmlEncode($this->aliases) . ');';
  176. }
  177. $id = $this->options['id'];
  178. $js .= 'jQuery("#' . $id . '").' . self::PLUGIN_NAME . '(' . $this->_hashVar . ');';
  179. MaskedInputAsset::register($view);
  180. $view->registerJs($js);
  181. }
  182. }