欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Html5用户注册自动校验

程序员文章站 2022-03-23 21:02:03
抽时间写了一个带有自动校验功能的html5用户注册demo。使用到handlebars模板技术和手机验证码校验。 以下是效果截图: 1.页面代码:usersregister.hbs  ...

抽时间写了一个带有自动校验功能的html5用户注册demo。使用到handlebars模板技术和手机验证码校验。

以下是效果截图:

Html5用户注册自动校验

1.页面代码:usersregister.hbs
 

<!doctype html>
<!--[if ie 8 ]> <html lang="en" class="ie8"> <![endif]-->
<!--[if ie 9 ]> <html lang="en" class="ie9"> <![endif]-->
<!--[if (gt ie 9)|!(ie)]><!-->
<html lang="en">
<!--<![endif]-->
<head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8">
    <meta http-equiv="x-ua-compatible" content="ie=edge" />
    <title>用户注册</title>

    <!--[if lt ie 9]>
    <script src="/assets/scripts/html5shiv.js"></script>
    <![endif]-->

    <link href="/assets/styles/jquery.idealforms.min.css" rel="stylesheet" media="screen" />

    <style type="text/css">
        body {
            font: normal 15px/1.5 arial, helvetica, free sans, sans-serif;
            color: #222;
            overflow-y: scroll;
            padding: 60px 0 0 0;
        }

        .main {
            width: 560px;
            height: 480px;
            margin: -50px auto;
        }

        #my-form {
            width: 560px;
            height: 450px;
            margin: 0 auto;
            border: 1px solid #ccc;
            padding: 3em;
            border-radius: 3px;
            box-shadow: 0 0 2px rgba(0, 0, 0, .2);
        }
    </style>

    <script type="text/javascript" src="/assets/scripts/jquery-1.8.2.min.js"></script>
    <script type="text/javascript" src="/assets/scripts/jquery.idealforms.js"></script>
</head>

<body>
<!-- style="background-image: url(static/image/bg.jpg) -->
    <p class="main" >
        <p style="height:5px;text-align:center;font-size:25px"> 欢迎您注册!</p>
        <!-- begin form -->
        <form id="my-form" class="myform">
            <p>
                <label>用户名:</label><input id="username" name="username" type="text" />
            </p>
            <p>
                <!-- <label>密码:</label><input id="pass" name="password" type="password" /> -->
                <label>密码:</label><input id="pass" name="password" type="text" />
            </p>
            <p>
                <label>邮箱:</label><input id="email" name="email"
                                         data-ideal="required email" type="email" />
            </p>
            <p>
                <label>电话:</label><input id="telephone" type="text" name="phone" data-ideal="phone" />
            </p>
            <p>
                <label>供应商v码:</label><input id="vcode" type="text" name="vcode" data-ideal="vcode" />
            </p>
            <p>
                <label>真实姓名:</label><input id="truename" type="text" name="truename" data-ideal="truename" />
            </p>
            <p>
                <label>手机验证码:</label><input id="telcode" type="text" name="telcode" data-ideal="telcode" />
            </p>
            <p style="margin-bottom:5px;">
                <button id="gettelcode" type="button" style="margin-left:160px; margin-right:auto;" >获取手机校验码</button>
                <hr style="margin-top:5px; margin-bottom:5px;" />
            </p>
            <!--<p>
                <label>性别:</label>
                <select id="sex" name="sex">
                    <option value="男">男</option>
                    <option value="女">女</option>
                </select>
            </p>
            <p>
                <label>昵称:</label><input id="nickname" type="text" name="nickname" data-ideal="nickname" />
            </p>
            <p>
                <label>年龄:</label><input id="age" type="text" name="age" data-ideal="age" />
            </p>-->
            <!-- <p>
                <label>地址:</label><input type="text" name="address" data-ideal="address" />
            </p>
            <p>
                <label>qq:</label><input type="text" name="qq" data-ideal="qq" />
            </p>
            <p>
                <label>邮编:</label><input type="text" name="zip" data-ideal="zip" />
            </p>
            <p>
                <label>传真:</label><input type="text" name="fax" data-ideal="fax" />
            </p>
            <p>
                <label>身份证:</label><input type="text" name="creditid" data-ideal="creditid" />
            </p>
            <p>
                <label>出生日期:</label><input name="date" class="datepicker"
                    data-ideal="date" type="text" placeholder="月/日/年" />
            </p>
            <p>
                <label>上传头像:</label><input id="file" name="file" multiple
                    type="file" />
            </p>
            <p>
                <label>个人主页:</label><input name="website" data-ideal="url"
                    type="text" />
            </p>
            <p>
                <label>备注:</label>
                <textarea id="comments" name="comments"></textarea>
            </p>
            -->
            <!-- <p id="languages">
                <label>语言:</label> <label><input type="checkbox"
                    name="langs[]" value="english" />英文</label> <label><input
                    type="checkbox" name="langs[]" value="chinese" />中文</label> <label><input
                    type="checkbox" name="langs[]" value="spanish" />西班牙文</label> <label><input
                    type="checkbox" name="langs[]" value="french" />法文</label>
            </p>
            <p>
                <label>精通几门:</label> <label><input type="radio"
                    name="radio" checked />1</label> <label><input type="radio"
                    name="radio" />2</label> <label><input type="radio" name="radio" />3</label>
                <label><input type="radio" name="radio" />4</label>
            </p>
            <p>
                <label>国籍:</label> <select id="states" name="states">
                    <option value="default">– 选择国籍 –</option>
                    <option value="al">阿拉伯</option>
                    <option value="ak">中国</option>
                    <option value="az">美国</option>
                    <option value="ar">法国</option>
                    <option value="ca">英国</option>
                    <option value="co">德国</option>
                    <option value="ct">西班牙</option>
                    <option value="de">俄罗斯</option>
                </select>
            </p> -->
            <p style="margin-top:10px; margin-left:100px;margin-right:100px;">
                <button type="button" id="submit" class="submit">提交</button>
                <button id="reset" type="button" >重置</button>
            </p>

        </form>
        <!-- end form -->
    </p>

<script type="text/javascript">
    var options = {

        onfail : function() {
            alert($myform.getinvalid().length + ' invalid fields.')
        },

        inputs : {
            'password' : {
                filters : 'required pass'
            },
            'username' : {
                filters : 'required username'
            },
            'email' : {
                filters : 'required email'
            },
            'phone' : {
                filters : 'required phone'
            },
            'truename' : {
                filters : 'required'
            },
            'vcode' : {
                filters : 'required'
            },
            'telcode' : {
                filters : 'required'
            }

            /*
            'age' : {
                filters : 'required digits',
                data : {
                   min : 16,
                   max : 70
                }
            },
            'file' : {
                filters : 'extension',
                data : {
                    extension : [ 'jpg' ]
                }
            },
            'comments' : {
                filters : 'min max',
                data : {
                    min : 50,
                    max : 200
                }
            },
            'states' : {
                filters : 'exclude',
                data : {
                    exclude : [ 'default' ]
                },
                errors : {
                    exclude : '选择国籍.'
                }
            },
            'langs[]' : {
                filters : 'min max',
                data : {
                    min : 2,
                    max : 3
                },
                errors : {
                    min : 'check at least <strong>2</strong> options.',
                    max : 'no more than <strong>3</strong> options allowed.'
                }
            }
            */
        }
    };

    $('#gettelcode').click(function() {
        var telephone = document.getelementbyid("telephone").value;   //手机号码
        if (telephone == null || telephone == ""){
            alert("手机号码不能为空!");
        }
        else{
            $.ajax({
                type : "get",
                datatype : "json",
                url : "../api/gettelcode?telephone="+ telephone,
                success : function(msg) {
                },
                error : function(e) {
                    alert("获取手机校验码失败!" + e);
                }
            });
        }
    });

    var $myform = $('#my-form').idealforms(options).data('idealforms');

    $('#submit').click(function() {
        var username = document.getelementbyid("username").value; //用户名
        var password = document.getelementbyid("pass").value;    //密码
        var email = document.getelementbyid("email").value;     //邮箱
        var telephone = document.getelementbyid("telephone").value;     //手机号码
        var vcode = document.getelementbyid("vcode").value;     //公司v码
        var telcode = document.getelementbyid("telcode").value;     //手机校验码
        var truename = document.getelementbyid("truename").value;     //真实姓名

        $.ajax({
            type : "get",
            url : "../api/usersregister?username="+ username +"&password="+ password +"&email="+ email +"&telephone="+ telephone +"&vcode="+ vcode +"&telcode="+ telcode +"&truename="+ truename,

            success : function(msg) {
               //获取当前网址,如: https://localhost:8083/uimcardprj/share/meun.jsp
               var curwwwpath = window.document.location.href;
               //获取主机地址之后的目录,如: uimcardprj/share/meun.jsp
               var pathname = window.document.location.pathname;
               var pos = curwwwpath.indexof(pathname);
               //获取主机地址,如: https://localhost:8083
               var localhostpaht = curwwwpath.substring(0, pos);
               //获取带"/"的项目名,如:/uimcardprj
               var projectname = pathname.substring(0, pathname.substr(1).indexof('/') + 1);
               window.location.href = projectname + "/login";
               alert("注册成功!");
            },
            error : function(e) {
                alert("注册失败!" + e);
            }
        });
    });

    $('#reset').click(function() {
        $myform.reset().fresh().focusfirst();
    });

</script>

</body>
</html>

 

2.jq输入校验:jquery.idealforms.js

 

该js校验初始版本来自cedric ruiz,我略有修改。

部分校验的规则如下:

  required: '此处是必填的.',
  number: '必须是数字.',
  digits: '必须是唯一的数字.',
  name: '必须至少有3个字符长,并且只能包含字母.',
  username: '用户名最短5位,最长30位,请使用英文字母、数字、中文和下划线. 用户名首字符必须为字母、数字、中文,不能为全数字.中文最长21个字.',
  pass: '密码的位数必须的在6-15位之间,并且至少包含一个数字,一个大写字母和一个小写字母.',
  strongpass: '必须至少为8个字符长,至少包含一个大写字母和一个小写字母和一个数字或特殊字符.',
  email: '必须是一个有效的email地址. (例: user@gmail.com)',
  phone: '必须是一个有效的手机号码. (例: 18723101212)'
以下是整个代码文件:
/*--------------------------------------------------------------------------

  jq-idealforms 2.1

  * author: cedric ruiz
  * license: gpl or mit
  * demo: https://elclanrs.github.com/jq-idealforms/
  *
--------------------------------------------------------------------------*/

;(function ( $, window, document, undefined ) {

  'use strict';

  // global ideal forms namespace
  $.idealforms = {}
  $.idealforms.filters = {}
  $.idealforms.errors = {}
  $.idealforms.flags = {}
  $.idealforms.ajaxrequests = {}

/*--------------------------------------------------------------------------*/

/**
 * @namespace a chest for various utils
 */
var utils = {
  /**
   * get width of widest element in the collection.
   * @memberof utils
   * @param {jquery object} $elms
   * @returns {number}
   */
  getmaxwidth: function( $elms ) {
    var maxwidth = 0
    $elms.each(function() {
      var width = $(this).outerwidth()
      if ( width > maxwidth ) {
        maxwidth = width
      }
    })
    return maxwidth
  },
  /**
   * hacky way of getting less variables
   * @memberof utils
   * @param {string} name the name of the less class.
   * @param {string} prop the css property where the data is stored.
   * @returns {number, string}
   */
  getlessvar: function( name, prop ) {
    var value = $('<p class="' + name + '"></p>').hide().appendto('body').css( prop )
    $('.' + name).remove()
    return ( /^\d+/.test( value ) ? parseint( value, 10 ) : value )
  },
  /**
   * like es5 object.keys
   */
  getkeys: function( obj ) {
    var keys = []
    for(var key in obj) {
      if ( obj.hasownproperty( key ) ) {
        keys.push( key )
      }
    }
    return keys
  },
  // get lenght of an object
  getobjsize: function( obj ) {
    var size = 0, key;
    for ( key in obj ) {
      if ( obj.hasownproperty( key ) ) {
        size++;
      }
    }
    return size;
  },
  isfunction: function( obj ) {
    return typeof obj === 'function'
  },
  isregex: function( obj ) {
    return obj instanceof regexp
  },
  isstring: function( obj ) {
    return typeof obj === 'string'
  },
  getbynameorid: function( str ) {
    var $el = $('[name="'+ str +'"]').length
      ? $('[name="'+ str +'"]') // by name
      : $('#'+ str) // by id
    return $el.length
      ? $el
      : $.error('the field "'+ str + '" doesn\'t exist.')
  },
  getfieldsfromarray: function( fields ) {
    var f = []
    for ( var i = 0, l = fields.length; i < l; i++ ) {
      f.push( utils.getbynameorid( fields[i] ).get(0) )
    }
    return $( f )
  },
  converttoarray: function( obj ) {
    return object.prototype.tostring.call( obj ) === '[object array]'
      ? obj : [ obj ]
  },
  /**
   * determine type of any ideal forms element
   * @param $input jquery $input object
   */
  getidealtype: function( $el ) {
    var type = $el.attr('type') || $el[0].tagname.tolowercase()
    return (
      /(text|password|email|number|search|url|tel|textarea)/.test( type ) && 'text' ||
      /file/.test( type ) && 'file' ||
      /select/.test( type ) && 'select' ||
      /(radio|checkbox)/.test( type ) && 'radiocheck' ||
      /(button|submit|reset)/.test( type ) && 'button' ||
      /h\d/.test( type ) && 'heading' ||
      /hr/.test( type ) && 'separator' ||
      /hidden/.test( type ) && 'hidden'
    )
  },
  /**
   * generates an input
   * @param name `name` attribute of the input
   * @param type `type` or `tagname` of the input
   */
  makeinput: function( name, value, type, list, placeholder ) {

    var markup, items = [], item, i, len

    function splitvalue( str ) {
      var item, value, arr
      if ( /::/.test( str ) ) {
        arr = str.split('::')
        item = arr[ 0 ]
        value = arr[ 1 ]
      } else {
        item = value = str
      }
      return { item: item, value: value }
    }

    // text & file
    if ( /^(text|password|email|number|search|url|tel|file|hidden)$/.test(type) )
      markup = '<input '+
        'type="'+ type +'" '+
        'id="'+ name +'" '+
        'name="'+ name +'" '+
        'value="'+ value +'" '+
        (placeholder && 'placeholder="'+ placeholder +'"') +
        '/>'

    // textarea
    if ( /textarea/.test( type ) ) {
      markup = '<textarea id="'+ name +'" name="'+ name +'" value="'+ value +'"></textarea>'
    }

    // select
    if ( /select/.test( type ) ) {
      items = []
      for ( i = 0, len = list.length; i < len; i++ ) {
        item = splitvalue( list[ i ] ).item
        value = splitvalue( list[ i ] ).value
        items.push('<option value="'+ value +'">'+ item +'</option>')
      }
      markup =
        '<select id="'+ name +'" name="'+ name +'">'+
          items.join('') +
        '</select>'
    }

    // radiocheck
    if ( /(radio|checkbox)/.test( type ) ) {
      items = []
      for ( i = 0, len = list.length; i < len; i++ ) {
        item = splitvalue( list[ i ] ).item
        value = splitvalue( list[ i ] ).value
        items.push(
          '<label>'+
            '<input type="'+ type +'" name="'+ name +'" value="'+ value +'" />'+
            item +
          '</label>'
        )
      }
      markup = items.join('')
    }

    return markup
  }
}

/**
 * custom tabs for ideal forms
 */
$.fn.idealtabs = function (container) {

  var

  // elements
  $contents = this,
  $container = container,
  $wrapper = $('<ul class="ideal-tabs-wrap"/>'),
  $tabs = (function () {
    var tabs = []
    $contents.each(function () {
      var name = $(this).attr('name')
      var html =
        '<li class="ideal-tabs-tab">'+
          '<span>' + name + '</span>'+
          '<i class="ideal-tabs-tab-counter ideal-tabs-tab-counter-zero">0</i>'+
        '</li>'
      tabs.push(html)
    })
    return $(tabs.join(''))
  }()),

  actions = {
    getcuridx: function () {
      return $tabs
        .filter('.ideal-tabs-tab-active')
        .index()
    },
    gettabidxbyname: function (name) {
      var re = new regexp(name, 'i')
      var $tab = $tabs.filter(function () {
        return re.test($(this).text())
      })
      return $tab.index()
    }
  },

  /**
   * public methods
   */
  methods = {
    /**
     * switch tab
     */
    switchtab: function (nameoridx) {

      var idx = utils.isstring(nameoridx)
        ? actions.gettabidxbyname(nameoridx)
        : nameoridx

      $tabs.removeclass('ideal-tabs-tab-active')
      $tabs.eq(idx).addclass('ideal-tabs-tab-active')
      $contents.hide().eq(idx).show()
    },

    nexttab: function () {
      var idx = actions.getcuridx() + 1
      idx > $tabs.length - 1
        ? methods.firsttab()
        : methods.switchtab(idx)
    },

    prevtab: function () {
      methods.switchtab(actions.getcuridx() - 1)
    },

    firsttab: function () {
      methods.switchtab(0)
    },

    lasttab: function () {
      methods.switchtab($tabs.length - 1)
    },

    updatecounter: function (nameoridx, text) {
      var idx = !isnan(nameoridx) ? nameoridx : actions.gettabidxbyname(name),
          $counter = $tabs.eq(idx).find('.ideal-tabs-tab-counter')
      $counter.removeclass('ideal-tabs-tab-counter-zero')
      if (!text) {
        $counter.addclass('ideal-tabs-tab-counter-zero')
      }
      $counter.html(text)
    }
  }

  // attach methods
  for (var m in methods)
    $contents[m] = methods[m]

  // init
  $tabs.first()
    .addclass('ideal-tabs-tab-active')
    .end()
    .click(function () {
      var name = $(this).text()
      $contents.switchtab(name)
    })

  // insert in dom & events
  $wrapper.append($tabs).appendto($container)

  $contents.addclass('ideal-tabs-content')
  $contents.each(function () {
    var $this = $(this), name = $(this).attr('name')
    $this.data('ideal-tabs-content-name', name)
      .removeattr('name')
  })
  $contents.hide().first().show() // start fresh

  return $contents

}

/**
 * a custom <select> menu jquery plugin
 * @example `$('select').idealselect()`
 */
$.fn.idealselect = function () {

  return this.each(function () {

    var

    $select = $(this),
    $options = $select.find('option')

    /**
     * generate markup and return elements of custom select
     * @memberof $.fn.tocustomselect
     * @returns {object} all elements of the new select replacement
     */
    var idealselect = (function () {
      var
      $wrap = $('<ul class="ideal-select '+ $select.attr('name') +'"/>'),
      $menu = $(
        '<li><span class="ideal-select-title">' +
          $options.filter(':selected').text() +
        '</span></li>'
      ),
      items = (function () {
        var items = []
        $options.each(function () {
          var $this = $(this)
          items.push('<li class="ideal-select-item">' + $this.text() + '</li>')
        })
        return items
      }())

      $menu.append('<ul class="ideal-select-sub">' + items.join('') + '</ul>')
      $wrap.append($menu)

      return {
        select: $wrap,
        title: $menu.find('.ideal-select-title'),
        sub: $menu.find('.ideal-select-sub'),
        items: $menu.find('.ideal-select-item')
      }
    }())

    /**
     * @namespace methods of custom select
     * @memberof $.fn.tocustomselect
     */
    var actions = {

      getselectedidx: function () {
        return idealselect.items
          .filter('.ideal-select-item-selected').index()
      },

      /**
       * @private
       */
      init: (function () {
        $select.css({
          position: 'absolute',
          left: '-9999px'
        })
        idealselect.sub.hide()
        idealselect.select.insertafter($select)
        idealselect.select.css(
          'min-width',
          utils.getmaxwidth(idealselect.items)
        )
        idealselect.items
          .eq($options.filter(':selected').index())
          .addclass('ideal-select-item-selected')
      }()),

      nowindowscroll: function (e) {
        if (e.which === 40 || e.which === 38 || e.which === 13) {
          e.preventdefault()
        }
      },

      // fix loosing focus when scrolling
      // and selecting item with keyboard
      focushack: function () {
        settimeout(function () {
          $select.trigger('focus')
        }, 1)
      },

      focus: function () {
        idealselect.select.addclass('ideal-select-focus')
        $(document).on('keydown.noscroll', actions.nowindowscroll)
      },

      blur: function () {
        idealselect.select
          .removeclass('ideal-select-open ideal-select-focus')
        $(document).off('.noscroll')
      },

      scrollintoview: function (dir) {
        var
        $selected = idealselect.items.filter('.ideal-select-item-selected'),
        itemheight = idealselect.items.outerheight(),
        menuheight = idealselect.sub.outerheight(),

        isinview = (function () {
          // relative position to the submenu
          var elpos = $selected.position().top + itemheight
          return dir === 'down'
            ? elpos <= menuheight
            : elpos > 0
        }())

        if (!isinview) {
          itemheight = (dir === 'down')
            ? itemheight // go down
            : -itemheight // go up

          idealselect.sub
            .scrolltop(idealselect.sub.scrolltop() + itemheight)
        }
      },

      scrolltoitem: function () {
        var idx = actions.getselectedidx(),
            height = idealselect.items.outerheight(),
            nitems = idealselect.items.length,
            allheight = height * nitems,
            curheight = height * (nitems - idx)

        idealselect.sub.scrolltop(allheight - curheight)
      },

      showmenu: function () {
        idealselect.sub.fadein('fast')
        idealselect.select.addclass('ideal-select-open')
        actions.select(actions.getselectedidx())
        actions.scrolltoitem()
      },

      hidemenu: function () {
        idealselect.sub.hide()
        idealselect.select.removeclass('ideal-select-open')
      },

      select: function (idx) {
        idealselect.items
          .removeclass('ideal-select-item-selected')
        idealselect.items
          .eq(idx).addclass('ideal-select-item-selected')
      },

      change: function (idx) {
        var text = idealselect.items.eq(idx).text()
        actions.select(idx)
        idealselect.title.text(text)
        $options.eq(idx).prop('selected', true)
        $select.trigger('change')
      },

      keydown: function (key) {
        var

        idx = actions.getselectedidx(),
        ismenu = idealselect.select.is('.ideal-select-menu'),
        isopen = idealselect.select.is('.ideal-select-open')

        /**
         * @namespace key pressed
         */
        var keys = {

          9: function () { // tab
            if (ismenu) {
              actions.blur()
              actions.hidemenu()
            }
          },

          13: function () { // enter
            if (ismenu)
              isopen
                ? actions.hidemenu()
                : actions.showmenu()
            actions.change(idx)
          },

          27: function () { // esc
            if (ismenu) actions.hidemenu()
          },

          40: function () { // down
            if (idx < $options.length - 1) {
              isopen
                ? actions.select(idx + 1)
                : actions.change(idx + 1)
            }
            actions.scrollintoview('down')
          },

          38: function () { // up
            if (idx > 0) {
              isopen
                ? actions.select(idx - 1)
                : actions.change(idx - 1)
            }
            actions.scrollintoview('up')
          },

          'default': function () { // letter
            var

            letter = string.fromcharcode(key),

            $matches = idealselect.items
              .filter(function () {
                return /^\w+$/i.test( letter ) && // not allow modifier keys ( ctrl, cmd, meta, super... )
                  new regexp('^' + letter, 'i').test( $(this).text() ) // find first match
              }),
            nmatches = $matches.length,

            counter = idealselect.select.data('counter') + 1 || 0,
            curkey = idealselect.select.data('key') || key,

            newidx = $matches.eq(counter).index()

            if (!nmatches) // no matches
              return false

            // if more matches with same letter
            if (curkey === key) {
              if (counter < nmatches) {
                idealselect.select.data('counter', counter)
              }
              else {
                idealselect.select.data('counter', 0)
                newidx = $matches.eq(0).index()
              }
            }
            // if new letter
            else {
              idealselect.select.data('counter', 0)
              newidx = $matches.eq(0).index()
            }

            if (isopen)
              actions.select(newidx)
            else
              actions.change(newidx)

            idealselect.select.data('key', key)

            actions.scrolltoitem()
            actions.focushack()
          }
        }

        keys[key]
          ? keys[key]()
          : keys['default']()
      }
    }

    /**
     * @namespace holds all events of custom select for "menu mode" and "list mode"
     * @memberof $.fn.tocustomselect
     */
    var events = {
      focus: actions.focus,
      'blur.menu': function () {
        actions.blur()
        actions.hidemenu()
      },
      'blur.list': function () {
        actions.blur()
      },
      keydown: function (e) {
        actions.keydown(e.which)
      },
      'clickitem.menu': function () {
        actions.change($(this).index())
        actions.hidemenu()
      },
      'clickitem.list': function () {
        actions.change($(this).index())
      },
      'clicktitle.menu': function () {
        actions.focus()
        actions.showmenu()
        $select.trigger('focus')
      },
      'hideoutside.menu': function () {
        $select.off('blur.menu')
        $(document).on('mousedown.ideal', function (evt) {
          if (!$(evt.target).closest(idealselect.select).length) {
            $(document).off('mousedown.ideal')
            $select.on('blur.menu', events['blur.menu'])
          } else {
            actions.focushack()
          }
        })
      },
      'mousedown.list': function () {
        actions.focushack()
      }
    }

    // reset events
    var disableevents = function () {
      idealselect.select.removeclass('ideal-select-menu ideal-select-list')
      $select.off('.menu .list')
      idealselect.items.off('.menu .list')
      idealselect.select.off('.menu .list')
      idealselect.title.off('.menu .list')
    }

    // menu mode
    idealselect.select.on('menu', function () {
      disableevents()
      idealselect.select.addclass('ideal-select-menu')
      actions.hidemenu()
      $select.on({
        'blur.menu': events['blur.menu'],
        'focus.menu': events.focus,
        'keydown.menu': events.keydown
      })
      idealselect.select.on('mousedown.menu', events['hideoutside.menu'])
      idealselect.items.on('click.menu', events['clickitem.menu'])
      idealselect.title.on('click.menu', events['clicktitle.menu'])
    })

    // list mode
    idealselect.select.on('list', function () {
      disableevents()
      idealselect.select.addclass('ideal-select-list')
      actions.showmenu()
      $select.on({
        'blur.list': events['blur.list'],
        'focus.list': events.focus,
        'keydown.list': events.keydown
      })
      idealselect.select.on('mousedown.list', events['mousedown.list'])
      idealselect.items.on('mousedown.list', events['clickitem.list'])
    })

    $select.keydown(function (e) {
      // prevent default keydown event
      // to avoid bugs with ideal select events
      if (e.which !== 9) e.preventdefault()
    })

    // reset
    idealselect.select.on('reset', function(){
      actions.change(0)
    })

    idealselect.select.trigger('menu') // default to "menu mode"
  })
}

/*
 * idealradiocheck: jquery plguin for checkbox and radio replacement
 * usage: $('input[type=checkbox], input[type=radio]').idealradiocheck()
 */
$.fn.idealradiocheck = function() {

  return this.each(function() {

    var $this = $(this)
    var $span = $('<span/>')

    $span.addclass( 'ideal-'+ ( $this.is(':checkbox') ? 'check' : 'radio' ) )
    $this.is(':checked') && $span.addclass('checked') // init
    $span.insertafter( $this )

    $this.parent('label').addclass('ideal-radiocheck-label')
      .attr('onclick', '') // fix clicking label in ios
    $this.css({ position: 'absolute', left: '-9999px' }) // hide by shifting left

    // events
    $this.on({
      change: function() {
        var $this = $(this)
        if ( $this.is('input[type="radio"]') ) {
          $this.parent().siblings('label').find('.ideal-radio').removeclass('checked')
        }
        $span.toggleclass( 'checked', $this.is(':checked') )
      },
      focus: function() { $span.addclass('focus') },
      blur: function() { $span.removeclass('focus') },
      click: function() { $(this).trigger('focus') }
    })
  })
}

;(function( $ ) {

  // browser supports html5 multiple file?
  var multiplesupport = typeof $('<input/>')[0].multiple !== 'undefined',
      isie = /msie/i.test( navigator.useragent )

  $.fn.idealfile = function() {

    return this.each(function() {

      var $file = $(this).addclass('ideal-file'), // the original file input
          // label that will be used for ie hack
          $wrap = $('<p class="ideal-file-wrap">'),
          $input = $('<input type="text" class="ideal-file-filename" />'),
          // button that will be used in non-ie browsers
          $button = $('<button type="button" class="ideal-file-upload">open</button>'),
          // hack for ie
          $label = $('<label class="ideal-file-upload" for="'+ $file[0].id +'">open</label>')

      // hide by shifting to the left so we
      // can still trigger events
      $file.css({
        position: 'absolute',
        left: '-9999px'
      })

      $wrap.append( $input, ( isie ? $label : $button ) ).insertafter( $file )

      // prevent focus
      $file.attr('tabindex', -1)
      $button.attr('tabindex', -1)

      $button.click(function () {
        $file.focus().click() // open dialog
      })

      $file.change(function() {

        var files = [], filearr, filename

        // if multiple is supported then extract
        // all filenames from the file array
        if ( multiplesupport ) {
          filearr = $file[0].files
          for ( var i = 0, len = filearr.length; i < len; i++ ) {
            files.push( filearr[i].name )
          }
          filename = files.join(', ')

        // if not supported then just take the value
        // and remove the path to just show the filename
        } else {
          filename = $file.val().split('\\').pop()
        }

        $input.val( filename ) // set the value
          .attr( 'title', filename ) // show filename in title tootlip

      })

      $input.on({
        focus: function () { $file.trigger('change') },
        blur: function () { $file.trigger('blur') },
        keydown: function( e ) {
          if ( e.which === 13 ) { // enter
            if ( !isie ) { $file.trigger('click') }
          } else if ( e.which === 8 || e.which === 46 ) { // backspace & del
            // on some browsers the value is read-only
            // with this trick we remove the old input and add
            // a clean clone with all the original events attached
            $file.replacewith( $file = $file.val('').clone( true ) )
            $file.trigger('change')
            $input.val('')
          } else if ( e.which === 9 ){ // tab
            return
          } else { // all other keys
            return false
          }
        }
      })

    })

  }

}( jquery ))


/**
 * @namespace errors
 * @locale en
 */
$.idealforms.errors = {
		
  required: '此处是必填的.',
  number: '必须是数字.',
  digits: '必须是唯一的数字.',
  name: '必须至少有3个字符长,并且只能包含字母.',
  username: '用户名最短5位,最长30位,请使用英文字母、数字、中文和下划线.用户名首字符必须为字母、数字、中文,不能为全数字.中文最长21个字.',
  pass: '密码的位数必须的在6-15位之间,并且至少包含一个数字,一个大写字母和一个小写字母.',
  strongpass: '必须至少为8个字符长,至少包含一个大写字母和一个小写字母和一个数字或特殊字符.',
  email: '必须是一个有效的email地址. <em>(例: user@gmail.com)</em>',
  phone: '必须是一个有效的手机号码. <em>(例: 18723101212)</em>',

  zip: 'must be a valid us zip code. <em>(e.g. 33245 or 33245-0003)</em>',
  url: 'must be a valid url. <em>(e.g. www.google.com)</em>',
  minchar: 'must be at least <strong>{0}</strong> characters long.',
  minoption: 'check at least <strong>{0}</strong> options.',
  maxchar: 'no more than <strong>{0}</strong> characters long.',
  maxoption: 'no more than <strong>{0}</strong> options allowed.',
  range: 'must be a number between {0} and {1}.',
  date: 'must be a valid date. <em>(e.g. {0})</em>',
  dob: 'must be a valid date of birth.',
  exclude: '"{0}" is not available.',
  excludeoption: '{0}',
  equalto: 'must be the same value as <strong>"{0}"</strong>',
  extension: 'file(s) must have a valid extension. <em>(e.g. "{0}")</em>',
  ajaxsuccess: '<strong>{0}</strong> is not available.',
  ajaxerror: 'server error...'

}

/**
 * get all default filters
 * @returns object
 */
var getfilters = function() {

  var filters = {

    required: {
      regex: /.+/,
      error: $.idealforms.errors.required
    },

    number: {
      regex: function( i, v ) { return !isnan(v) },
      error: $.idealforms.errors.number
    },

    digits: {
      regex: /^\d+$/,
      error: $.idealforms.errors.digits
    },

    name: {
      regex: /^[a-za-z]{3,}$/,
      error: $.idealforms.errors.name
    },

    username: {
      regex: /^[a-z](?=[\w.]{4,30}$)\w*\.?\w*$/i,
      error: $.idealforms.errors.username
    },

    pass: {
      regex: /(?=.*\d)(?=.*[a-z])(?=.*[a-z]).{6,}/,
      error: $.idealforms.errors.pass
    },

    strongpass: {
      regex: /(?=^.{8,}$)((?=.*\d)|(?=.*\w+))(?![.\n])(?=.*[a-z])(?=.*[a-z]).*$/,
      error: $.idealforms.errors.strongpass
    },

    email: {
      regex: /^([a-za-z0-9]*[-_.]?[a-za-z0-9]+)*@([a-za-z0-9]*[-_]?[a-za-z0-9]+)+[\\.][a-za-z]{2,3}([\\.][a-za-z]{2})?$/,
      error: $.idealforms.errors.email
    },

    phone: {
      //regex: /^((13[0-9])|(15[0-9])|(17[0-9])|(18[0-9]))\\d{8}$/,
      regex: /^(0|86|17951)?(13[0-9]|15[012356789]|17[678]|18[0-9]|14[57])[0-9]{8}$/,
      error: $.idealforms.errors.phone
    },

    zip: {
      regex: /^\d{5}$|^\d{5}-\d{4}$/,
      error: $.idealforms.errors.zip
    },

    url: {
      regex: /^(?:(ftp|http|https):\/\/)?(?:[\w\-]+\.)+[a-z]{2,6}([\:\/?#].*)?$/i,
      error: $.idealforms.errors.url
    },

    min: {
      regex: function( input, value ) {
        var $input = input.input,
            min = input.useroptions.data.min,
            isradiocheck = $input.is('[type="checkbox"], [type="radio"]')
        if ( isradiocheck ) {
          this.error = $.idealforms.errors.minoption.replace( '{0}', min )
          return $input.filter(':checked').length >= min
        }
        this.error = $.idealforms.errors.minchar.replace( '{0}', min )
        return value.length >= min
      }
    },

    max: {
      regex: function( input, value ) {
        var $input = input.input,
            max = input.useroptions.data.max,
            isradiocheck = $input.is('[type="checkbox"], [type="radio"]')
        if ( isradiocheck ) {
          this.error = $.idealforms.errors.maxoption.replace( '{0}', max )
          return $input.filter(':checked').length <= max
        }
        this.error = $.idealforms.errors.maxchar.replace( '{0}', max )
        return value.length <= max
      }
    },

    range: {
      regex: function( input, value ) {
        var range = input.useroptions.data.range,
            val = +value
        this.error = $.idealforms.errors.range
          .replace( '{0}', range[0] )
          .replace( '{1}', range[1] )
        return val >= range[0] && val <= range[1]
      }
    },

    date: {
      regex: function( input, value ) {
        var

        userformat =
          input.useroptions.data && input.useroptions.data.date
            ? input.useroptions.data.date
            : 'mm/dd/yyyy', // default format

        delimiter = /[^mdy]/.exec( userformat )[0],
        theformat = userformat.split(delimiter),
        thedate = value.split(delimiter),

        isdate = function( date, format ) {
          var m, d, y
          for ( var i = 0, len = format.length; i < len; i++ ) {
            if ( /m/.test( format[i]) ) m = date[i]
            if ( /d/.test( format[i]) ) d = date[i]
            if ( /y/.test( format[i]) ) y = date[i]
          }
          return (
            m > 0 && m < 13 &&
            y && y.length === 4 &&
            d > 0 && d <= ( new date( y, m, 0 ) ).getdate()
          )
        }

        this.error = $.idealforms.errors.date.replace( '{0}', userformat )

        return isdate( thedate, theformat )
      }
    },

    dob: {
      regex: function( input, value ) {
        var

        userformat =
          input.useroptions.data && input.useroptions.data.dob
            ? input.useroptions.data.dob
            : 'mm/dd/yyyy', // default format

        // simulate a date input
        dateinput = {
          input: input.input,
          useroptions: {
            data: { date: userformat }
          }
        },

        // use internal date filter to validate the date
        isdate = filters.date.regex( dateinput, value ),

        // dob
        theyear = /\d{4}/.exec( value ),
        maxyear = new date().getfullyear(), // current year
        minyear = maxyear - 100

        this.error = $.idealforms.errors.dob

        return isdate && theyear >= minyear && theyear <= maxyear
      }
    },

    exclude: {
      regex: function( input, value ) {
        var $input = input.input,
            exclude = input.useroptions.data.exclude,
            isoption = $input.is('[type="checkbox"], [type="radio"], select')
        this.error = isoption
          ? $.idealforms.errors.excludeoption.replace( '{0}', value )
          : this.error = $.idealforms.errors.exclude.replace( '{0}', value )
        return $.inarray( value, exclude ) === -1
      }
    },

    equalto: {
      regex: function( input, value ) {
        var $equals = $( input.useroptions.data.equalto ),
            $input = input.input,
            name = $equals.attr('name') || $equals.attr('id'),
            isvalid = $equals.parents('.ideal-field')
              .filter(function(){ return $(this).data('ideal-isvalid') === true })
              .length
        if ( !isvalid ) { return false }
        this.error = $.idealforms.errors.equalto.replace( '{0}', name )
        return $input.val() === $equals.val()
      }
    },

    extension: {
      regex: function( input, value ) {
        var files = input.input[0].files || [{ name: value }],
            extensions = input.useroptions.data.extension,
            re = new regexp( '\\.'+ extensions.join('|') +'$', 'i' ),
            valid = false
        for ( var i = 0, len = files.length; i < len; i++ ) {
          valid = re.test( files[i].name );
        }
        this.error = $.idealforms.errors.extension.replace( '{0}', extensions.join('", "') )
        return valid
      }
    },

    ajax: {
      regex: function( input, value, showorhideerror ) {

        var self = this
        var $input = input.input
        var useroptions = input.useroptions
        var name = $input.attr('name')
        var $field = $input.parents('.ideal-field')
        var valid = false

        var customerrors = useroptions.errors && useroptions.errors.ajax
        self.error = {}
        self.error.success = customerrors && customerrors.success
          ? customerrors.success
          : $.idealforms.errors.ajaxsuccess.replace( '{0}', value )
        self.error.fail = customerrors && customerrors.error
          ? customerrors.error
          : $.idealforms.errors.ajaxerror

        // send input name as $_post[name]
        var data = {}
        data[ name ] = $.trim( value )

        // ajax options defined by the user
        var userajaxops = input.useroptions.data.ajax

        var ajaxops = {
          type: 'post',
          datatype: 'json',
          data: data,
          success: function( resp, text, xhr ) {
          console.log(resp)
            showorhideerror( self.error.success, true )
            $input.data({
              'ideal-ajax-resp': resp,
              'ideal-ajax-error': self.error.success
            })
            $input.trigger('change') // to update counter
            $field.removeclass('ajax')
            // run custom success callback
            if( userajaxops._success ) {
              userajaxops._success( resp, text, xhr )
            }
          },
          error: function( xhr, text, error ) {
            if ( text !== 'abort' ) {
              showorhideerror( self.error.fail, false )
              $input.data( 'ideal-ajax-error', self.error.fail )
              $field.removeclass('ajax')
              // run custom error callback
              if ( userajaxops._error ) {
                userajaxops._error( xhr, text, error )
              }
            }
          }
        }
        $.extend( ajaxops, userajaxops )

        // init
        $input.removedata('ideal-ajax-error')
        $input.removedata('ideal-ajax-resp')
        $field.addclass('ajax')

        // run request and save it to be able to abort it
        // so requests don't bubble
        $.idealforms.ajaxrequests[ name ] = $.ajax( ajaxops )
      }
    }

  }

  return filters

}

$.idealforms.flags = {
  noerror: function (i) {
    i.parent().siblings('.ideal-error').hide()
  },
  noicons: function (i) {
    i.siblings('.ideal-icon-valid, .ideal-icon-invalid').hide()
  },
  novalidicon: function (i) {
    i.siblings('.ideal-icon-valid').hide()
  },
  noinvalidicon: function (i) {
    i.siblings('.ideal-icon-invalid').hide()
  },
  noclass: function (i) {
    i.parents('.ideal-field').removeclass('valid invalid')
  },
  novalidclass: function (i) {
    i.parents('.ideal-field').removeclass('valid')
  },
  noinvalidclass: function (i) {
    i.parents('.ideal-field').removeclass('invalid')
  }
}

/*
 * ideal forms plugin
 */
var _defaults = {
  inputs: {},
  customfilters: {},
  customflags: {},
  globalflags: '',
  onsuccess: function(e) { alert('thank you...') },
  onfail: function() { alert('invalid!') },
  responsiveat: 'auto',
  disablecustom: ''
}

// constructor
var idealforms = function( element, options ) {

  var self = this

  self.$form = $( element )
  self.opts = $.extend( {}, _defaults, options )

  self.$tabs = self.$form.find('section')

  // set localized filters
  $.extend( $.idealforms.filters, getfilters() )

  self._init()

}

// plugin
$.fn.idealforms = function( options ) {
  return this.each(function() {
    if ( !$.data( this, 'idealforms' ) ) {
      $.data( this, 'idealforms', new idealforms( this, options ) )
    }
  })
}

// get less variables
var lessvars = {
  fieldwidth: utils.getlessvar( 'ideal-field-width', 'width' )
}

/*
 * private methods
 */
$.extend( idealforms.prototype, {

  _init: function() {

    var self = this
    var o = self.opts
    var formelements = self._getformelements()

    self.$form.css( 'visibility', 'visible' )
      .addclass('ideal-form')
      .attr( 'novalidate', 'novalidate' ) // disable html5 validation

    // do markup
    formelements.inputs
      .add( formelements.headings )
      .add( formelements.separators )
      .each(function(){ self._domarkup( $(this) ) })

    // generate tabs
    if ( self.$tabs.length ) {
      var $tabcontainer = $('<p class="ideal-wrap ideal-tabs ideal-full-width"/>')
      self.$form.prepend( $tabcontainer )
      self.$tabs.idealtabs( $tabcontainer )
    }

    // always show datepicker below the input
    if ( jquery.ui ) {
      $.datepicker._checkoffset = function( a,b,c ) { return b }
    }

    // add inputs specified by data-ideal
    // to the list of user inputs
    self.$form.find('[data-ideal]').each(function() {
      var userinput = o.inputs[ this.name ]
      o.inputs[ this.name ] = userinput || { filters: $(this).data('ideal') }
    })

   // responsive
    if ( o.responsiveat ) {
      $(window).resize(function(){ self._responsive() })
      self._responsive()
    }

    // form events
    self.$form.on({
      keydown: function( e ) {
        // prevent submit when pressing enter
        // but exclude textareas
        if ( e.which === 13 && e.target.nodename !== 'textarea' ) {
          e.preventdefault()
        }
      },
      submit: function( e ) {
        if ( !self.isvalid() ) {
          e.preventdefault()
          o.onfail()
          self.focusfirstinvalid()
        } else {
          o.onsuccess( e )
        }
      }
    })

    self._adjust()
    self._attachevents()
    self.fresh() // start fresh

  },

  _getformelements: function() {
    return {
      inputs: this.$form.find('input, select, textarea, :button'),
      labels: this.$form.find('p > label:first-child'),
      text: this.$form.find('input:not([type="checkbox"], [type="radio"], [type="submit"]), textarea'),
      select: this.$form.find('select'),
      radiocheck: this.$form.find('input[type="radio"], input[type="checkbox"]'),
      buttons: this.$form.find(':button'),
      file: this.$form.find('input[type="file"]'),
      headings: this.$form.find('h1, h2, h3, h4, h5, h6'),
      separators: this.$form.find('hr'),
      hidden: this.$form.find('input:hidden')
    }
  },

  _getuserinputs: function() {
    return this.$form.find('[name="'+ utils.getkeys( this.opts.inputs ).join('"], [name="') +'"]')
  },

  _gettab: function( nameoridx ) {
    var self = this
    var isnumber = !isnan( nameoridx )
    if ( isnumber ) {
      return self.$tabs.eq( nameoridx )
    }
    return self.$tabs.filter(function() {
      var re = new regexp( nameoridx, 'i' )
      return re.test( $(this).data('ideal-tabs-content-name') )
    })
  },

  _getcurrenttabidx: function() {
    return this.$tabs.index( this.$form.find('.ideal-tabs-content:visible') )
  },

  _updatetabscounter: function() {
    var self = this
    self.$tabs.each(function( i ) {
      var invalid = self.getinvalidintab( i ).length
      self.$tabs.updatecounter( i, invalid )
    })
  },

  _adjust: function() {

    var self = this
    var o = self.opts
    var formelements = self._getformelements()
    var curtab = self._getcurrenttabidx()

    // autocomplete causes some problems...
    formelements.inputs.attr('autocomplete', 'off')

    // show tabs to calculate dimensions
    if ( self.$tabs.length ) { self.$tabs.show() }

    // adjust labels
    var labels = formelements.labels
    labels.removeattr('style').width( utils.getmaxwidth( labels ) )

    // adjust headings and separators
    if ( self.$tabs.length ) {
      this.$tabs.each(function(){
        $( this ).find('.ideal-heading:first').addclass('first-child')
      })
    } else {
      self.$form.find('.ideal-heading:first').addclass('first-child')
    }

    self._setdatepicker()

    // done calculating hide tabs
    if ( self.$tabs.length ) {
      self.$tabs.hide()
      self.switchtab( curtab )
    }

  },

  _setdatepicker: function() {

    var o = this.opts
    var $datepicker = this.$form.find('input.datepicker')

    if ( jquery.ui && $datepicker.length ) {

      $datepicker.each(function() {
        var userinput = o.inputs[ this.name ]
        var data = userinput && userinput.data && userinput.data.date
        var format = data ? data.replace( 'yyyy', 'yy' ) : 'mm/dd/yy'

        $(this).datepicker({
          dateformat: format,
          beforeshow: function( input ) {
            $( input ).addclass('open')
          },
          onchangemonthyear: function() {
            // hack to fix ie9 not resizing
            var $this = $(this)
            var w = $this.outerwidth() // cache first!
            settimeout(function() {
              $this.datepicker('widget').css( 'width', w )
            }, 1)
          },
          onclose: function() { $(this).removeclass('open') }
        })
      })

      // adjust width
      $datepicker.on('focus keyup', function() {
        var t = $(this), w = t.outerwidth()
        t.datepicker('widget').css( 'width', w )
      })

      $datepicker.parent().siblings('.ideal-error').addclass('hidden')
    }
  },

  _domarkup: function( $element ) {

    var o = this.opts
    var elementtype = utils.getidealtype( $element )

    // validation elements
    var $field = $('<span class="ideal-field"/>')
    var $error = $('<span class="ideal-error" />')
    var $valid = $('<i class="ideal-icon ideal-icon-valid" />')
    var $invalid = $('<i class="ideal-icon ideal-icon-invalid"/>')
      .click(function(){
        $(this).parent().find('input:first, textarea, select').focus()
      })

    // basic markup
    $element.closest('p').addclass('ideal-wrap')
      .children('label:first-child').addclass('ideal-label')

    var idealelements = {

      _defaultinput: function() {
        $element.wrapall( $field ).after( $valid, $invalid )
          .parent().after( $error )
      },

      text: function() { idealelements._defaultinput() },

      radiocheck: function() {
        // check if input is already wrapped so we don't
        // wrap radios and checks more than once
        var iswrapped = $element.parents('.ideal-field').length
        if ( !iswrapped ) {
          $element.parent().nextall().andself().wrapall( $field.addclass('ideal-radiocheck') )
          $element.parents('.ideal-field').append( $valid, $invalid ).after( $error )
        }
        if ( !/radiocheck/.test( o.disablecustom ) ) {
          $element.idealradiocheck()
        }
      },

      select: function() {
        idealelements._defaultinput()
        if ( !/select/.test( o.disablecustom ) ) {
          $element.idealselect()
        }
      },

      file: function() {
        idealelements._defaultinput()
        if ( !/file/.test( o.disablecustom ) ) {
          $element.idealfile()
        }
      },

      button: function() {
        if ( !/button/.test( o.disablecustom ) ) {
          $element.addclass('ideal-button')
        }
      },

      hidden: function() {
        $element.closest('p').addclass('ideal-hidden')
      },

      heading: function() {
        $element.closest('p').addclass('ideal-full-width')
        $element.parent().children().wrapall('<span class="ideal-heading"/>')
      },

      separator: function() {
        $element.closest('p').addclass('ideal-full-width')
        $element.wrapall('<p class="ideal-separator"/>')
      }

    }

    // generate markup for current element type
    idealelements[ elementtype ] ? idealelements[ elementtype ]() : $.noop()

    $error.add( $valid ).add( $invalid ).hide() // start fresh

  },


  /** validates an input and shows or hides error and icon
   * @memberof actions
   * @param {object} $input jquery object
   * @param {string} e the javascript event
   */
  _validate: function( $input, e ) {

    var self = this
    var o = this.opts

    var useroptions = o.inputs[ $input.attr('name') ]
    var userfilters = useroptions.filters && useroptions.filters.split(/\s/)
    var name = $input.attr('name')
    var value = $input.val()

    var ajaxrequest = $.idealforms.ajaxrequests[ name ]

    var isradiocheck = $input.is('[type="checkbox"], [type="radio"]')

    var inputdata = {
      // if is radio or check validate all inputs related by name
      input: isradiocheck ? self.$form.find('[name="' + name + '"]') : $input,
      useroptions: useroptions
    }

    // validation elements
    var $field = $input.parents('.ideal-field')
    var $error = $field.siblings('.ideal-error')
    var $invalid = isradiocheck
      ? $input.parent().siblings('.ideal-icon-invalid')
      : $input.siblings('.ideal-icon-invalid')
    var $valid = isradiocheck
      ? $input.parent().siblings('.ideal-icon-valid')
      : $input.siblings('.ideal-icon-valid')

    function reseterror() {
      $field.removeclass('valid invalid').removedata('ideal-isvalid')
      $error.add( $invalid ).add( $valid ).hide()
    }

    function showorhideerror( error, valid ) {
      reseterror()
      valid ? $valid.show() : $invalid.show()
      $field.addclass( valid ? 'valid' : 'invalid' )
      $field.data( 'ideal-isvalid', valid )
      if ( !valid ) {
        $error.html( error ).toggle( $field.is('.ideal-field-focus') )
      }
    }

    // prevent validation when typing but not introducing any new characters
    // this is mainly to prevent multiple ajax requests
    var oldvalue = $input.data('ideal-value') || 0
    $input.data( 'ideal-value', value )
    if ( e.type === 'keyup' && value === oldvalue ) { return false }

    // validate
    if ( userfilters ) {

      $.each( userfilters, function( i, filter ) {

        var thefilter = $.idealforms.filters[ filter ]
        var customerror = useroptions.errors && useroption