redamoon
5/11/2015 - 5:34 PM

BNjqaK

BNjqaK

BNjqaK ('-' * 6)

A Pen by redamoon on CodePen.

License.

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8" />
  <title></title>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script>
</head>
<body>
<div class="l-container">
  <div class="row">
    <div class="col">
      <label for="">アカウント作成</label>
    </div>
    <div class="col">
      <input type="text" placeholder="4文字以上8文字以内で入力" data-minlength="4" data-maxlength="8" required>
      <ul>
        <li data-error="required">必須です</li>
        <li data-error="minlength">4文字以上で入力</li>
        <li data-error="maxlength">8文字以内で入力</li>
      </ul>
    </div>
  </div>
  <div class="row">
    <div class="col">
      <label for="">パスワードを作成</label>
    </div>
    <div class="col">
      <input type="text" placeholder="4文字以上8文字以内で入力" data-minlength="4" data-maxlength="8" required>
      <ul>
        <li data-error="required">必須です</li>
        <li data-error="minlength">4文字以上で入力</li>
        <li data-error="maxlength">8文字以内で入力</li>
      </ul>
    </div>
  </div>
</div>
</body>
</html>
/*
 * Model
 */

function AppModel(attrs){
  this.val = "";
  this.attrs = {
    required: "",
    maxlength: 8,
    minlength: 4
  };
  this.listeners = {
    valid: [],
    invalid : []
  };
};
AppModel.prototype.on = function(event, func){
  this.listeners[event].push(func);
};
AppModel.prototype.trigger = function(event){
  $.each(this.listeners[event], function(){
    this();
  });
};
AppModel.prototype.set = function(val){
  if (this.val === val) return;
  this.val = val;
  this.validate();
};
AppModel.prototype.required = function(){
  return this.val !== "";
};
AppModel.prototype.maxlength = function(num){
  return num >= this.val.length;
};
AppModel.prototype.minlength = function(num){
  return num <= this.val.length;
};
AppModel.prototype.validate = function(){
  var val;
  this.errors = [];
  for (var key in this.attrs) {
    if(!this[key](val)) this.errors.push(key);
  }
  this.trigger(!this.errors.length ? "valid" : "invalid");
};


/*
 * View
 */
function AppView(el){
  this.initialize(el);
  this.handleEvents();
};
AppView.prototype.initialize = function(el){
  this.$el = $(el);
  this.$list = this.$el.next().children();
  var obj = this.$el.data();
  if (this.$el.prop("required")){
    obj["required"] = "";
  }
  this.model = new AppModel(obj);
};
AppView.prototype.handleEvents = function(){
  var self = this;
  this.$el.on("keyup", function(e){
    self.onKeyup(e);
  });
  
  this.model.on("valid", function(){
    self.onValid();
  });
  this.model.on("invalid", function(){
    self.onInvalid();
  })
};
AppView.prototype.onKeyup = function(e){
  var $target = $(e.currentTarget);
  this.model.set($target.val());
};
AppView.prototype.onValid = function(){
  this.$el.removeClass("error");
  this.$list.hide();
};
AppView.prototype.onInvalid = function(){
  var self = this;
  this.$el.removeClass("error");
  this.$list.hide();
  $.each(this.model.errors, function(index, val) {
    self.$list.filter("[data-error=\"" + val + "\"]").show();
  });
};


$("input").each(function(){
  new AppView(this);
})
ul{
  margin:0;
  padding:0;
  list-style: none;
}
.l-container{
  padding:40px;
}
h1{
  font-size:16px;
  font-weight: bold;
}
.row{
  margin-top: 2em;
  display: table;
  width: 100%;
}
.row .col{
  display: table-cell;
}
.row .col:first-child{
  width: 200px;
}
.row .col label{
  font-weight: bold;
}
.row .col input{
  font-size: 100%;
  outline: none;
  border: none;
  border-bottom: 1px solid #999;
  padding: 0.5em 0;
  min-width: 400px;
}
.row .col input.error{
  border-color: #e51c23;
}
.row .col ul{
  margin-top:0.5em;
}
.row .col li{
  display:none;
  color: #e51c23;
  margin-top: 0.5em;
}
.row .col li:first-child {
  margin-top:0;
}