Meteor Reactivity Playground 2.0

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/underscore.js/1.7.0/underscore-min.js"></script>
<script src="http://www.chicagogrooves.com/js/mini-meteor.min.js"></script>
<!-- 
   Check out the video explaining these at:
   http://bit.ly/meteor-form-validation-video
-->
<form>
    <h1>Meteor Reactive Form Validation</h1>
    <div id="error-messages" class="error">
        Please review the required fields:
    </div>
    <div>
        <label for="question_11">
            <span class="required">*</span>
            What is your name:
        </label>
        <input name="question_11" id="question_11" autofocus="1"/>
    </div>
    <div>
        <label for="question_22">What is your BFF's name:</label>
        <input name="question_22" id="question_22"/>
    </div>
    <div>
        <label for="question_33">
            <span class="required">*</span>
            You are a child of the:
        </label>
        <select name="question_33" id="question_33">
            <option value="">Select One</option>
            <option value="2000">2000's</option>
            <option value="1990">1990's</option>
            <option value="1980">1980's</option>
            <option value="1970">1970's</option>
            <option value="1960">1960's</option>
            <option value="1950">1950's</option>
            <option value="-1">an earlier era</option>
        </select>
    </div>
    <div>
        <input type="submit"/>
    </div>
</form>





<a id="fs" style="float: left; margin-top: 2em" href="http://jsfiddle.net/chicagogrooves/y18er7wr/embedded/result/" target="_blank">Launch Full Screen -&gt;</a>

<a id="sc" style="float: right; margin-top: 2em" href="http://bit.ly/meteor-form-validation-video" target="_blank">See Screencast</a>

CSS

body{ margin-top: 25px; margin-left: 10px;}
.error, .required{ color: red; }
input[type=submit]{ margin-top: 20px; }
input, select{ margin-right: 20px; }

JavaScript

// keys are ids, values are true if in error
var errorStates = initErrorStates();

// define and wireup event handler
$("form").on("submit", validationHandler);
function validationHandler(e){
    validateFields();
    console.log(errorStates.allValid() ? "will submit" : "no submit 4 you");
    if(e.type=="submit"){ e.preventDefault(); }

};

// set up an instance of ReactiveDict suitable
// for initial and future validation
// (for display purposes, fields start *not* in error)
function initErrorStates(){
    var states = new ReactiveMap();
    
    states.set(11, false);
    states.set(33, false);
    
    states.allValid = function(){
        for(var key in states.keys){
            if(states.get(key)==true)
                return false;
        }
        return true;
    }
    return states;
}

// a simple loop, setting errors for any ids lacking a value
function validateFields(){
    for(var key in errorStates.keys){
        errorStates.set(key, false);
        var answer=$("[name=question_" + key + "]").val();
        if(!answer){
            errorStates.set(key, true);
        }
    }
}

function setFieldState(key){
    var inError = errorStates.get(key);
    var methodName = inError ? "addClass" : "removeClass";
    var label = $("[for=question_" + key + "]");
    label[methodName]("error");
}

function setFormState(){
    var style = errorStates.allValid() ? "hidden" : "visible";
    $("#error-messages").css("visibility", style);
}

function reactify(){
    //set up reactivity for form as a whole
    Meteor.run(setFormState);

    // set up reactivity for each field
    for(var key in errorStates.keys){
        Meteor.run(setFieldState.bind({}, key));
    }
}
reactify();