diff --git a/.travis.yml b/.travis.yml
index 82e211951..0902d63d5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -30,9 +30,9 @@ jobs:
php: 7.1
install:
# Install Nette Code Checker
- - travis_retry composer create-project nette/code-checker temp/code-checker ~2 --no-progress
+ - travis_retry composer create-project nette/code-checker temp/code-checker ^3 --no-progress
# Install Nette Coding Standard
- - travis_retry composer create-project nette/coding-standard temp/coding-standard --no-progress
+ - travis_retry composer create-project nette/coding-standard temp/coding-standard ^2 --no-progress
# Install new Node.js
- . $HOME/.nvm/nvm.sh
- nvm install stable
@@ -40,8 +40,8 @@ jobs:
# Install Grunt and Eslint
- npm install -g grunt-cli; cd tests/netteForms; npm install; cd ../..
script:
- - php temp/code-checker/src/code-checker.php --short-arrays
- - php temp/coding-standard/ecs check src tests examples --config temp/coding-standard/coding-standard-php56.neon
+ - php temp/code-checker/code-checker
+ - php temp/coding-standard/ecs check src tests examples --config temp/coding-standard/coding-standard-php56.yml
- grunt --gruntfile=tests/netteForms/Gruntfile.js test
- tests/netteForms/node_modules/.bin/eslint src/assets/netteForms.js --config tests/.eslintrc.js
diff --git a/package.json b/package.json
index b7aaa1969..6307002cf 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "nette-forms",
- "version": "2.4.2",
+ "version": "2.4.9",
"description": "Client side script for Nette Forms Component",
"keywords": [
"nette",
diff --git a/src/Forms/Controls/BaseControl.php b/src/Forms/Controls/BaseControl.php
index ace5d7eeb..4cbb4e08d 100644
--- a/src/Forms/Controls/BaseControl.php
+++ b/src/Forms/Controls/BaseControl.php
@@ -334,7 +334,8 @@ public function setHtmlId($id)
public function getHtmlId()
{
if (!isset($this->control->id)) {
- $this->control->id = sprintf(self::$idMask, $this->lookupPath());
+ $form = $this->getForm(false);
+ $this->control->id = sprintf(self::$idMask, $this->lookupPath(), $form ? $form->getName() : '');
}
return $this->control->id;
}
diff --git a/src/Forms/Form.php b/src/Forms/Form.php
index 2ba2cb3ee..7a6078a8c 100644
--- a/src/Forms/Form.php
+++ b/src/Forms/Form.php
@@ -44,6 +44,7 @@ class Form extends Container implements Nette\Utils\IHtmlString
EMAIL = ':email',
URL = ':url',
PATTERN = ':pattern',
+ PATTERN_ICASE = ':patternCaseInsensitive',
INTEGER = ':integer',
NUMERIC = ':integer',
FLOAT = ':float',
diff --git a/src/Forms/Validator.php b/src/Forms/Validator.php
index 3a6b2ee5f..5b4272174 100644
--- a/src/Forms/Validator.php
+++ b/src/Forms/Validator.php
@@ -270,14 +270,21 @@ public static function validateUrl(IControl $control)
/**
- * Matches control's value regular expression?
+ * Does the control's value match the regular expression?
+ * Case-sensitive to comply with the HTML5 pattern attribute behaviour
* @param string
* @return bool
*/
- public static function validatePattern(IControl $control, $pattern)
+ public static function validatePattern(IControl $control, $pattern, $caseInsensitive = false)
{
$value = $control->getValue() instanceof Nette\Http\FileUpload ? $control->getValue()->getName() : $control->getValue();
- return (bool) Strings::match($value, "\x01^(?:$pattern)\\z\x01u");
+ return (bool) Strings::match($value, "\x01^(?:$pattern)\\z\x01u" . ($caseInsensitive ? 'i' : ''));
+ }
+
+
+ public static function validatePatternCaseInsensitive(IControl $control, $pattern)
+ {
+ return self::validatePattern($control, $pattern, true);
}
diff --git a/src/assets/netteForms.js b/src/assets/netteForms.js
index 95255f3df..383e86730 100644
--- a/src/assets/netteForms.js
+++ b/src/assets/netteForms.js
@@ -455,12 +455,36 @@
} catch (e) {} // eslint-disable-line no-empty
},
- pattern: function(elem, arg, val) {
+ pattern: function(elem, arg, val, value, caseInsensitive) {
+ if (typeof arg !== 'string') {
+ return null;
+ }
+
try {
- return typeof arg === 'string' ? (new RegExp('^(?:' + arg + ')$')).test(val) : null;
+ try {
+ var regExp = new RegExp('^(?:' + arg + ')$', caseInsensitive ? 'ui' : 'u');
+ } catch (e) {
+ regExp = new RegExp('^(?:' + arg + ')$', caseInsensitive ? 'i' : '');
+ }
+
+ if (window.FileList && val instanceof FileList) {
+ for (var i = 0; i < val.length; i++) {
+ if (!regExp.test(val[i].name)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ return regExp.test(val);
} catch (e) {} // eslint-disable-line no-empty
},
+ patternCaseInsensitive: function(elem, arg, val) {
+ return Nette.validators.pattern(elem, arg, val, null, true);
+ },
+
integer: function(elem, arg, val) {
if (elem.type === 'number' && elem.validity.badInput) {
return false;
diff --git a/src/assets/netteForms.min.js b/src/assets/netteForms.min.js
index a364c61e7..0eb72b0ef 100644
--- a/src/assets/netteForms.min.js
+++ b/src/assets/netteForms.min.js
@@ -2,20 +2,21 @@
(function(e,p){if(e.JSON)if("function"===typeof define&&define.amd)define(function(){return p(e)});else if("object"===typeof module&&"object"===typeof module.exports)module.exports=p(e);else{var d=!e.Nette||!e.Nette.noInit;e.Nette=p(e);d&&e.Nette.initOnLoad()}})("undefined"!==typeof window?window:this,function(e){function p(a){return function(b){return a.call(this,b)}}var d={formErrors:[],version:"2.4",addEvent:function(a,b,c){"DOMContentLoaded"===b&&"loading"!==a.readyState?c.call(this):a.addEventListener?
a.addEventListener(b,c):"DOMContentLoaded"===b?a.attachEvent("onreadystatechange",function(){"complete"===a.readyState&&c.call(this)}):a.attachEvent("on"+b,p(c))},getValue:function(a){var b;if(a){if(a.tagName){if("radio"===a.type){var c=a.form.elements;for(b=0;bb?null:
c[b].value;for(b=0;b=b},maxLength:function(a,b,c){if("number"===a.type){if(a.validity.tooLong)return!1;
+a.getAttribute&&b===a.getAttribute("data-nette-empty-value")&&(b="");return b},validateControl:function(a,b,c,f,q){a=a.tagName?a:a[0];b=b||d.parseJSON(a.getAttribute("data-nette-rules"));f=void 0===f?{value:d.getEffectiveValue(a)}:f;for(var g=0,k=b.length;g=b},maxLength:function(a,b,c){if("number"===a.type){if(a.validity.tooLong)return!1;
if(a.validity.badInput)return null}return c.length<=b},length:function(a,b,c){if("number"===a.type){if(a.validity.tooShort||a.validity.tooLong)return!1;if(a.validity.badInput)return null}b=d.isArray(b)?b:[b,b];return(null===b[0]||c.length>=b[0])&&(null===b[1]||c.length<=b[1])},email:function(a,b,c){return/^("([ !#-[\]-~]|\\[ -~])+"|[-a-z0-9!#$%&'*+/=?^_`{|}~]+(\.[-a-z0-9!#$%&'*+/=?^_`{|}~]+)*)@([0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)+[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?$/i.test(c)},
url:function(a,b,c,d){/^[a-z\d+.-]+:/.test(c)||(c="http://"+c);return/^https?:\/\/((([-_0-9a-z\u00C0-\u02FF\u0370-\u1EFF]+\.)*[0-9a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,61}[0-9a-z\u00C0-\u02FF\u0370-\u1EFF])?\.)?[a-z\u00C0-\u02FF\u0370-\u1EFF]([-0-9a-z\u00C0-\u02FF\u0370-\u1EFF]{0,17}[a-z\u00C0-\u02FF\u0370-\u1EFF])?|\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}|\[[0-9a-f:]{3,39}\])(:\d{1,5})?(\/\S*)?$/i.test(c)?(d.value=c,!0):!1},regexp:function(a,b,c){a="string"===typeof b?b.match(/^\/(.*)\/([imu]*)$/):
-!1;try{return a&&(new RegExp(a[1],a[2].replace("u",""))).test(c)}catch(f){}},pattern:function(a,b,c){try{return"string"===typeof b?(new RegExp("^(?:"+b+")$")).test(c):null}catch(f){}},integer:function(a,b,c){return"number"===a.type&&a.validity.badInput?!1:/^-?[0-9]+$/.test(c)},"float":function(a,b,c,d){if("number"===a.type&&a.validity.badInput)return!1;c=c.replace(/ +/g,"").replace(/,/g,".");return/^-?[0-9]*\.?[0-9]+$/.test(c)?(d.value=c,!0):!1},min:function(a,b,c){if("number"===a.type){if(a.validity.rangeUnderflow)return!1;
-if(a.validity.badInput)return null}return null===b||parseFloat(c)>=b},max:function(a,b,c){if("number"===a.type){if(a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return null===b||parseFloat(c)<=b},range:function(a,b,c){if("number"===a.type){if(a.validity.rangeUnderflow||a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return d.isArray(b)?(null===b[0]||parseFloat(c)>=b[0])&&(null===b[1]||parseFloat(c)<=b[1]):null},submitted:function(a){return a.form["nette-submittedBy"]===
-a},fileSize:function(a,b,c){if(e.FileList)for(a=0;ab)return!1;return!0},image:function(a,b,c){if(e.FileList&&c instanceof e.FileList)for(a=0;a=b},max:function(a,b,c){if("number"===a.type){if(a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return null===b||parseFloat(c)<=b},range:function(a,
+b,c){if("number"===a.type){if(a.validity.rangeUnderflow||a.validity.rangeOverflow)return!1;if(a.validity.badInput)return null}return d.isArray(b)?(null===b[0]||parseFloat(c)>=b[0])&&(null===b[1]||parseFloat(c)<=b[1]):null},submitted:function(a){return a.form["nette-submittedBy"]===a},fileSize:function(a,b,c){if(e.FileList)for(a=0;ab)return!1;return!0},image:function(a,b,c){if(e.FileList&&c instanceof e.FileList)for(a=0;avalue = '123x';
+ Assert::false(Validator::validatePatternCaseInsensitive($control, '[0-9]'));
+ Assert::true(Validator::validatePatternCaseInsensitive($control, '[0-9]+x'));
+ Assert::true(Validator::validatePatternCaseInsensitive($control, '[0-9]+X'));
+});
+
test(function () {
class MockUploadControl extends UploadControl
diff --git a/tests/Forms/Forms.idMask.phpt b/tests/Forms/Forms.idMask.phpt
new file mode 100644
index 000000000..ed7ddbbbe
--- /dev/null
+++ b/tests/Forms/Forms.idMask.phpt
@@ -0,0 +1,55 @@
+getHtmlId();
+}, Nette\InvalidStateException::class, "Component '' is not attached to ''.");
+
+
+test(function () {
+ $container = new Nette\Forms\Container;
+ $container->setParent(null, 'second');
+ $input = $container->addText('name');
+ Assert::same('frm-name', $input->getHtmlId());
+});
+
+
+test(function () {
+ $form = new Form;
+ $container = $form->addContainer('second');
+ $input = $container->addText('name');
+ Assert::same('frm-second-name', $input->getHtmlId());
+});
+
+
+test(function () {
+ $form = new Form;
+ $input = $form->addText('name');
+ Assert::same('frm-name', $input->getHtmlId());
+});
+
+
+test(function () {
+ Nette\Forms\Controls\BaseControl::$idMask = 'frm-%s-%s';
+
+ $form = new Form;
+ $input = $form->addText('name');
+ Assert::same('frm-name-', $input->getHtmlId());
+});
+
+
+test(function () {
+ Nette\Forms\Controls\BaseControl::$idMask = 'frm-%2$s-%1$s';
+
+ $form = new Form('signForm');
+ $input = $form->addText('name');
+ Assert::same('frm-signForm-name', $input->getHtmlId());
+});
diff --git a/tests/netteForms/spec/Nette.validateRuleSpec.js b/tests/netteForms/spec/Nette.validateRuleSpec.js
index 938c286db..03fdace3f 100644
--- a/tests/netteForms/spec/Nette.validateRuleSpec.js
+++ b/tests/netteForms/spec/Nette.validateRuleSpec.js
@@ -45,6 +45,14 @@ describe('Nette.getValue & validateRule', function() {
expect(Nette.validateRule(el, 'pattern', '\\d')).toBe(false);
expect(Nette.validateRule(el, 'pattern', '\\w')).toBe(false);
expect(Nette.validateRule(el, 'pattern', '\\w+')).toBe(true);
+ expect(Nette.validateRule(el, 'pattern', 'hello')).toBe(true);
+ expect(Nette.validateRule(el, 'pattern', 'HELLO')).toBe(false);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', '\\d+')).toBe(false);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', '\\d')).toBe(false);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', '\\w')).toBe(false);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', '\\w+')).toBe(true);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', 'hello')).toBe(true);
+ expect(Nette.validateRule(el, 'patternCaseInsensitive', 'HELLO')).toBe(true);
expect(Nette.validateRule(el, 'integer')).toBe(false);
expect(Nette.validateRule(el, 'float')).toBe(false);