プログラミング/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()