プログラミング/Materialize のバックアップの現在との差分(No.2)

更新


  • 追加された行はこの色です。
  • 削除された行はこの色です。
[[公開メモ]]

* validate の方法 [#baca2da5]

これとか、

https://codepen.io/tdurant72/pen/vgyBjZ

これとかのようにするみたい。

http://demo.geekslabs.com/materialize-v1.0/form-validation.html

jQuery.validate はこちら。

https://jqueryvalidation.org/validate/

** Validation ルール [#i324aa63]

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 の中味 [#n5de8f94]

$('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 に保持される。

*** コンストラクタ [#q048c609]

 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 にうまく効かない [#ne2d8a7b]

https://github.com/Dogfalo/materialize/issues/1861

Materialize では select 本体が非表示になっているので validation がスキップされてしまう

上記を参考に

 LANG:coffee
 $('.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:coffee
        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 )

こうしないとダメだった

こういうのは、

 LANG:coffee
   $.extend $.validator.defaults,
     errorClass: 'invalid'
     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 )
     errorPlacement: (error, element)->
       $(element)
         .closest('form')
         .find("label[for='#{ element.attr('id') }']")
         .attr('data-error', error.text())

なんて書いてやれば毎回書く必要がなくなるみたい。

さらに、click や change イベントが起きないので、

 LANG:coffee
    @on 'mount', ->
      $('li', @root).on 'click', ->
        $('select', @root).click()

のようにして、li の click で select の click を呼び出すようにした。

** autocomplete が日本語でうまく動かない [#r57e663c]

keyup で引っかけているので ime の確定時にも keyup を呼んでやる必要がある

 LANG:coffee
        $('input', @root).on 'compositionend', ->
          $('input', @root).keyup()


Counter: 3341 (from 2010/06/03), today: 1, yesterday: 0