プログラミング/Materialize

(2212d) 更新


公開メモ

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: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 が日本語でうまく動かない

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

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

Counter: 3337 (from 2010/06/03), today: 2, yesterday: 0