SundayBird / project

0 stars 0 forks source link

[2018.03.10] SignUp Form Validation with ReactiveX #10

Closed pjhjohn closed 5 years ago

pjhjohn commented 5 years ago

Goal

ReactiveX 를 이용하여 여러 Observable / Operator 를 사용해보도록 하자. 어떤 Observable / Operator 를 사용하는지는 과제의 요구사항에 있지는 않으니 자유롭게 구현하면 된다. 각 항목에서 자유롭게 심화 구현을 해도 된다.

Task

Email & Password 를 입력받아 로그인 시도를 흉내내는 UI를 만든다.

화면 구성 : 총 6개 View

동작

kyunooh commented 5 years ago

CSS로 구현하기 https://jsfiddle.net/jmo5tp9f/

kyunooh commented 5 years ago
<input type="email" minlength="4" required="required" />
<span class="msg">Email is required</span>

<input type="password" minlength="4" required="required" />
<span class="msg">Password is too short</span>

input {
  display: block;
}

input:valid + span.msg {
  display: none;
}

input:not(:valid) + span.msg {
  display: initial;
}
kyunooh commented 5 years ago

http://tech.kakao.com/2017/01/09/daummovie-rxjs/ 요거가 예시가 참 좋군요

kyunooh commented 5 years ago
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Reactive X Count</title>
  <style>
    .hidden {
      display: none;
    }

    .error-msg {
      color: red;
    }
  </style>
</head>
<body>
<div>
  <label for="email">Email</label>
  <input id="email" type="email" placeholder="Email" pattern=".*(\.\w+)$" required>
  <p id="email-error" class="error-msg hidden"></p>
</div>
<div>
  <label for="password">Password</label>
  <input id="password" type="password" placeholder="Password" minlength="8" maxlength="64" required>
  <p id="password-error" class="error-msg hidden" ></p>
</div>
<div>
  <button id="submit" class="hidden">Submit</button>
  <p id="result-msg" class="hidden"></p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.min.js"></script>
<script>
    const submitBtn = document.getElementById("submit");
    const inputs = document.getElementsByTagName("input");
    const inputArray = Array.from(inputs);
    const resultMsg = document.getElementById("result-msg");

    const registerValidation = (inputId) => {
        const input = document.getElementById(inputId);
        Rx.Observable
            .fromEvent(input, "keyup")
            .debounce(1000)
            .map((e) => {
                const valid = inputArray.every(input => input.validity.valid);
                const showBtn = inputArray.every(input => input.value);

                if (showBtn) {
                    submitBtn.classList.remove("hidden");
                }

                submitBtn.disabled = !valid;
                return e;
            })
            .map((e) => e.target)
            .forEach((target) => {
                const errorEl = document.getElementById(`${inputId}-error`);
                const errorClassList = errorEl.classList;
                if (target.validity.vaild) {
                    errorClassList.add("hidden");
                } else {
                    if(target.validity.patternMismatch) {
                      errorEl.textContent = "올바른 이메일 형식이 아닙니다."
                    } else {
                      errorEl.textContent = target.validationMessage;
                    }
                    errorClassList.remove("hidden");
                }
            });
    };
    registerValidation("email");
    registerValidation("password");

    Rx.Observable
        .fromEvent(submitBtn, "click")
        .map(() => {
          console.log("map");
          inputArray.forEach(input => {
              input.disabled = true;
          });
          submitBtn.disabled = true;
        })
        .flatMapFirst(Rx.Observable.fromPromise( () =>
            new Promise((resolve, reject) => {
              setTimeout(() => {
                  console.log("asrtonuyasrtuyon")
                  const d = new Date();
                  if (d.getTime() % 2 === 0) {
                      resolve(true);
                  } else {
                      reject(false);
                  }
              }, 5000);
            })
        ).take(1))
        .subscribe(
            onNext = (result) => {
                activateInputsAndButtons();
                resultMsg.textContent = "이야 드디어 내가 해냈어!";
                resultMsg.classList.remove("hidden");
            },
            onError = (err) => {
                resultMsg.textContent = "아닛 내가 실패라니 그게 무슨소리오!";
                resultMsg.classList.remove("hidden");
            },
            onCompleted = () => {
              activateInputsAndButtons();
            }
        );

    const activateInputsAndButtons = () => {
        inputArray.forEach(input => {
            input.disabled = false;
        });
        submitBtn.disabled = false;
    }

</script>
</body>
</html>
pjhjohn commented 5 years ago
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Reactive X Count</title>
  <style>
    .hidden {
      display: none;
    }

    .error-msg {
      color: red;
    }
  </style>
</head>
<body>
  <div>
    <label for="email">Email</label>
    <input id="email" type="email" placeholder="Email" pattern=".*(\.\w+)$" required>
    <p id="email-error" class="error-msg hidden"></p>
  </div>
  <div>
    <label for="password">Password</label>
    <input id="password" type="password" placeholder="Password" minlength="8" maxlength="64" required>
    <p id="password-error" class="error-msg hidden" ></p>
  </div>
  <div>
    <button id="submit" class="hidden">Submit</button>
    <p id="result-msg" class="hidden"></p>
  </div>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/4.1.0/rx.all.min.js"></script>
  <script>
    const emailInput = document.getElementById("email");
    const passwordInput = document.getElementById("password");
    const submitBtn = document.getElementById("submit");
    const resultMsg = document.getElementById("result-msg");

    const networkStateIdle = "IDLE";
    const networkStatePending = "PENDING";
    const networkStateSubject = new Rx.BehaviorSubject(networkStateIdle);

    const observeValidationResultOf = (input) => {
      return Rx.Observable
        .fromEvent(input, "keyup")
        .debounce(1000)
        .map((e) => {
          const valid = input.validity.valid
          const target = e.target
          const errorEl = document.getElementById(`${input.id}-error`);
          const errorClassList = errorEl.classList;

          if (valid) {
            errorClassList.add("hidden");
          } else {
            if(target.validity.patternMismatch) {
              errorEl.textContent = "올바른 이메일 형식이 아닙니다."
            } else {
              errorEl.textContent = target.validationMessage;
            }
            errorClassList.remove("hidden");
          }
          return valid;
        })
    }

    // Submit Button Click => Submit Button Enablability 결정 (서버다녀옴)
    Rx.Observable
      .fromEvent(submitBtn, "click")
      .doOnNext(() => {
        networkStateSubject.onNext(networkStatePending);
      })
      .flatMap(Rx.Observable.fromPromise(() =>
        new Promise((resolve, reject) => {
          setTimeout(() => {
            console.log("asrtonuyasrtuyon")
            const d = new Date();
            if (d.getTime() % 2 === 0) {
                resolve(true);
            } else {
                reject(false);
            }
          }, 5000);
        })
      ).take(1))
      .subscribe(
        onNext = (result) => {
          networkStateSubject.onNext(networkStateIdle);
          resultMsg.textContent = "이야 드디어 내가 해냈어!";
          resultMsg.classList.remove("hidden");
        },
        onError = (err) => {
          networkStateSubject.onNext(networkStateIdle);
          resultMsg.textContent = "아닛 내가 실패라니 그게 무슨소리오!";
          resultMsg.classList.remove("hidden");
        },
        onCompleted = () => {
          networkStateSubject.onNext(networkStateIdle);
        }
      );

    // Submit Button Enablability
    Rx.Observable.combineLatest(
      observeValidationResultOf(emailInput), // Action Dispather
      observeValidationResultOf(passwordInput), // Action Dispather
      networkStateSubject, // Action Dispather
      (emailIsValid, passwordIsValid, networkState) => {
        // Reducer
        return emailIsValid && passwordIsValid && networkState === networkStateIdle;
      }
    ).subscribe(
      onNext = (valid) => {
        // State Update
        submitBtn.classList.remove("hidden");
        submitBtn.disabled = !valid;
      }
    )

    // Email & Password Input Enability
    networkStateSubject.subscribe(
      onNext = (networkState) => {
        const disabled = networkState === networkStatePending;
        emailInput.disabled = disabled;
        passwordInput.disabled = disabled;
      }
    );
  </script>
</body>
</html>