プログラミング/Materialize の履歴(No.3)
更新validate の方法†
これとか、
https://codepen.io/tdurant72/pen/vgyBjZ
これとかのようにするみたい。
http://demo.geekslabs.com/materialize-v1.0/form-validation.html
jQuery.validate はこちら。
https://jqueryvalidation.org/validate/
Validation ルール†
https://jqueryvalidation.org/documentation/#link-list-of-built-in-validation-methods
標準的なルール:
- required – Makes the element required.
- remote – Requests a resource to check the element for validity.
- minlength – Makes the element require a given minimum length.
- maxlength – Makes the element require a given maximum length.
- rangelength – Makes the element require a given value range.
- min – Makes the element require a given minimum.
- max – Makes the element require a given maximum.
- range – Makes the element require a given value range.
- step – Makes the element require a given step.
- email – Makes the element require a valid email
- url – Makes the element require a valid url
- date – Makes the element require a date.
- dateISO – Makes the element require an ISO date.
- number – Makes the element require a decimal number.
- digits – Makes the element require digits only.
- equalTo – Requires the element to be the same as another one
その他のルール:
https://cdnjs.cloudflare.com/ajax/libs/jquery-validate/1.17.0/additional-methods.js
を読めば使える。
https://github.com/jquery-validation/jquery-validation/tree/master/src/additional
- accept
- additional
- alphanumeric
- bankaccountNL
- bankorgiroaccountNL
- bic
- cifES
- cpfBR
- creditcard
- creditcardtypes
- currency
- dateFA
- dateITA
- dateNL
- extension
- giroaccountNL
- greaterThan
- greaterThanEqual
- iban
- integer
- ipv4
- ipv6
- lessThan
- lessThanEqual
- lettersonly
- letterswithbasicpunc
- maxfiles
- maxsize
- maxsizetotal
- mobileNL
- mobileUK
- netmask
- nieES
- nifES
- nipPL
- notEqualTo
- nowhitespace
- pattern - 正規表現
- phoneNL
- phonePL
- phoneUK
- phoneUS
- phonesUK
- postalCodeCA
- postalcodeBR
- postalcodeIT
- postalcodeNL
- postcodeUK
- require_from_group
- skip_or_fill_minimum
- statesUS
- strippedminlength
- time
- time12h
- url2
- vinUS
- zipcodeUS
- ziprange
例えば、
LANG:javascript $.validator.addMethod( "ipv4", function( value, element ) { return this.optional( element ) || /^(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)\.(25[0-5]|2[0-4]\d|[01]?\d\d?)$/i.test( value ); }, "Please enter a valid IP v4 address." );
こんな感じで独自ルールを追加できるので、とても便利だ。
jQuery.validate の中味†
$('form#id').validate( options ) は、
LANG:javascript // https://jqueryvalidation.org/validate/ validate: function( options ) {
を呼び出す。
すでに form が validator を持っていると何もしないので注意。
LANG:javascript validator = new $.validator( options, this[ 0 ] );
として validator が作られる。
作られた validator は $.data( form, "validator", validator ) のように form に保持される。
コンストラクタ†
LANG:javascript // Constructor for validator $.validator = function( options, form ) { this.settings = $.extend( true, {}, $.validator.defaults, options ); this.currentForm = form; this.init(); };
options は validator.settings に保存される。
init() は、
LANG:javascript $.extend( $.validator, { ... prototype: { init: function() { ... rules = this.settings.rules; $.each( rules, function( key, value ) { rules[ key ] = $.validator.normalizeRule( value ); } ); ... $( this.currentForm ) .on( "focusin.validate focusout.validate keyup.validate", ":text, [type='password'], [type='file'], select, textarea, [type='number'], [type='search'], " + "[type='tel'], [type='url'], [type='email'], [type='datetime'], [type='date'], [type='month'], " + "[type='week'], [type='time'], [type='datetime-local'], [type='range'], [type='color'], " + "[type='radio'], [type='checkbox'], [contenteditable], [type='button']", delegate ) // Support: Chrome, oldIE // "select" is provided as event.target when clicking a option .on( "click.validate", "select, option, [type='radio'], [type='checkbox']", delegate );
のようにして currentForm 以下の各種入力フィールドにイベントハンドラを結びつける
LANG:javascript var currentForm = this.currentForm ... function delegate( event ) { // Set form expando on contenteditable if ( !this.form && this.hasAttribute( "contenteditable" ) ) { this.form = $( this ).closest( "form" )[ 0 ]; this.name = $( this ).attr( "name" ); } // Ignore the element if it belongs to another form. This will happen mainly // when setting the `form` attribute of an input to the id of another form. if ( currentForm !== this.form ) { return; } var validator = $.data( this.form, "validator" ), eventType = "on" + event.type.replace( /^validate/, "" ), settings = validator.settings; if ( settings[ eventType ] && !$( this ).is( settings.ignore ) ) { settings[ eventType ].call( validator, this, event ); } }
settings[ eventType ] のデフォルト値は、
LANG:javascript onfocusin: function( element ) { this.lastActive = element; // Hide error label and remove error class on focus if enabled if ( this.settings.focusCleanup ) { if ( this.settings.unhighlight ) { this.settings.unhighlight.call( this, element, this.settings.errorClass, this.settings.validClass ); } this.hideThese( this.errorsFor( element ) ); } }, onfocusout: function( element ) { if ( !this.checkable( element ) && ( element.name in this.submitted || !this.optional( element ) ) ) { this.element( element ); } }, onkeyup: function( element, event ) { // Avoid revalidate the field when pressing one of the following keys // Shift => 16 // Ctrl => 17 // Alt => 18 // Caps lock => 20 // End => 35 // Home => 36 // Left arrow => 37 // Up arrow => 38 // Right arrow => 39 // Down arrow => 40 // Insert => 45 // Num lock => 144 // AltGr key => 225 var excludedKeys = [ 16, 17, 18, 20, 35, 36, 37, 38, 39, 40, 45, 144, 225 ]; if ( event.which === 9 && this.elementValue( element ) === "" || $.inArray( event.keyCode, excludedKeys ) !== -1 ) { return; } else if ( element.name in this.submitted || element.name in this.invalid ) { this.element( element ); } }, onclick: function( element ) { // Click on selects, radiobuttons and checkboxes if ( element.name in this.submitted ) { this.element( element ); // Or option elements, check parent select in that case } else if ( element.parentNode.name in this.submitted ) { this.element( element.parentNode ); } },
this.element( element ) は、
LANG:javascript // https://jqueryvalidation.org/Validator.element/ element: function( element ) { var cleanElement = this.clean( element ), checkElement = this.validationTargetFor( cleanElement ), v = this, result = true, rs, group; if ( checkElement === undefined ) { delete this.invalid[ cleanElement.name ]; } else { this.prepareElement( checkElement ); this.currentElements = $( checkElement ); // If this element is grouped, then validate all group elements already // containing a value group = this.groups[ checkElement.name ]; if ( group ) { $.each( this.groups, function( name, testgroup ) { if ( testgroup === group && name !== checkElement.name ) { cleanElement = v.validationTargetFor( v.clean( v.findByName( name ) ) ); if ( cleanElement && cleanElement.name in v.invalid ) { v.currentElements.push( cleanElement ); result = v.check( cleanElement ) && result; } } } ); } rs = this.check( checkElement ) !== false; result = result && rs; if ( rs ) { this.invalid[ checkElement.name ] = false; } else { this.invalid[ checkElement.name ] = true; } if ( !this.numberOfInvalids() ) { // Hide error containers on last error this.toHide = this.toHide.add( this.containers ); } this.showErrors(); // Add aria-invalid status for screen readers $( element ).attr( "aria-invalid", !rs ); } return result; },
this.check( element ) は、
LANG:javascript check: function( element ) { element = this.validationTargetFor( this.clean( element ) ); var rules = $( element ).rules(), rulesCount = $.map( rules, function( n, i ) { return i; } ).length, ... for ( method in rules ) { rule = { method: method, parameters: rules[ method ] }; try { result = $.validator.methods[ method ].call( this, val, element, rule.parameters ); ...
初めに呼ばれている $( element ).rules() は、
LANG:javascript rules: function( command, argument ) { var element = this[ 0 ], ... data = $.validator.normalizeRules( $.extend( {}, $.validator.classRules( element ), $.validator.attributeRules( element ), $.validator.dataRules( element ), $.validator.staticRules( element ) ), element ); ... return data; } } );
classRules により、element の class に、
- required: { required: true },
- email: { email: true },
- url: { url: true },
- date: { date: true },
- dateISO: { dateISO: true },
- number: { number: true },
- digits: { digits: true },
- creditcard: { creditcard: true }
が含まれている場合にチェックする。
attributeRules により、element の attr に、$.validator.methods に含まれる名前が含まれていればチェックする。
dataRules により、element が data-ruleRequired などを含む場合にチェックする。
staticRules により、validator.settings.rules[ element.name ] に含まれるチェックをする。
LANG:javascript // https://jqueryvalidation.org/Validator.showErrors/ showErrors: function( errors ) { if ( errors ) { var validator = this; // Add items to error list and map $.extend( this.errorMap, errors ); this.errorList = $.map( this.errorMap, function( message, name ) { return { message: message, element: validator.findByName( name )[ 0 ] }; } ); // Remove items from success list this.successList = $.grep( this.successList, function( element ) { return !( element.name in errors ); } ); } if ( this.settings.showErrors ) { this.settings.showErrors.call( this, this.errorMap, this.errorList ); } else { this.defaultShowErrors(); } },
select にうまく効かない†
https://github.com/Dogfalo/materialize/issues/1861
Materialize では select 本体が非表示になっているので validation がスキップされてしまう
上記を参考に
LANG:javascript $('.select-wrapper > select').css({ display: 'inline', position: 'absolute', float: 'left', padding: 0, margin: 0, border: '1px solid rgba(255,255,255,0)', height: 0, width: 0, top: '2em', left: '3em', opacity: 0 });
として無理矢理表示すれば validation はかかるのだけれど、、、
Materialize の css では .select-wrapper に .invalid を付けないとハイライトされない
LANG:javascript highlight: (element, errorClass, validClass)-> target = if element.type == 'radio' @findByName( element.name ) else if element.type == 'select-one' or element.type == 'select-multiple' $( element ).closest('div.select-wrapper') else $( element ) target.addClass( errorClass ).removeClass( validClass ) unhighlight: (element, errorClass, validClass)-> target = if element.type == 'radio' @findByName( element.name ) else if element.type == 'select-one' or element.type == 'select-multiple' $( element ).closest('div.select-wrapper') else $( element ) target.removeClass( errorClass ).addClass( validClass )
こうしないとダメだった
さらに、click や change イベントが起きないので、
LANG:javascript @on 'mount', -> $('li', @root).on 'click', -> $('select', @root).click()
のようにして、li の click で select の click を呼び出すようにした。
autocomplete が日本語でうまく動かない†
keyup で引っかけているので ime の確定時にも keyup を呼んでやる必要がある
LANG:javascript $('input', @root).on 'compositionend', -> $('input', @root).keyup()