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);