"The JavaScript Problem"
The truth is that we've been gradually doing more and more functional-style programming for years.
The view is a pure function of the model
In real-world applications data flow is not one-way
How does data make its way back to the model/server?
?
↓
@cowboyd
I believe we've already met.
$('form').on('submit', function() {
$.post('/api/cards', $(this).serializeJSON());
});
$('form').validator({
"input[name=card-number]": {
validations: {
lunCheck: function(value) {/* isValidLuhn(value)*/ }
},
errorMessageSelector: ".card-number-errors"
} //etc.
}).on('submit', function(e) {
const validation = $(this).validate(); //validation plugin
if (validation.passed) {
$.post('/api/cards', $(this).serializeJSON());
} else {
e.preventDefault();
}
});
I want validation to happen every time a field changes
$('form').validator({
"input[name=card-number]": {
validations: {
lunCheck: function(value) {/* isValidLuhn(value)*/ }
},
errorMessageSelector: ".card-name-errors"
} //etc.
}).on('submit', function(e) {
const validation = $(this).validate(); //validation plugin
if (validation.passed) {
$.post('/api/cards', $(this).serializeJSON());
} else {
e.preventDefault();
}
}).on('change', function() {
const validation = $(this).validate();
});
Don't enable the submit button unless the form is actually valid.
$('form').validator({
"input[name=card-number]": {
validations: {
lunCheck: function(value) {/* isValidLuhn(value)*/ }
},
errorMessageSelector: ".card-name-errors"
} //etc.
}).on('submit', function(e) {
const validation = $(this).validate(); //validation plugin
if (validation.passed) {
$.post('/api/cards', $(this).serializeJSON());
} else {
e.preventDefault();
}
}).on('change', function() {
const validation = $(this).validate();
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
});
Let's verify with the server that this card isn't in our fraud database.
let isSafeCardNumber = false;
$('form').validator({
"input[name=card-number]": {
validations: {
lunCheck: function(value) {/* isValidLuhn(value)*/ }
},
errorMessageSelector: ".card-name-errors"
} //etc.
}).on('submit', function(e) {
const validation = $(this).validate(); //validation plugin
if (validation.passed && isSafeCardNumber) {
$.post('/api/cards', $(this).serializeJSON());
} else {
e.preventDefault();
}
}).on('change', function() {
const validation = $(this).validate();
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
}).on('change', function(e) {
const validation = $(this).validate();
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
const creditCardNumber = this.elements['card-number'];
if (e.target === creditCardNumber) {
$.get(`/api/fraud-detector/cards/${creditCardNumber.value}`).then(()=> {
isSafeCardNumber = true;
}).fail(()=> {
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
isSafeCardNumber = false;
});
}
});
Can we throw up a loading spinner while we're doing the fraud detection?
let isSafeCardNumber = false;
$('form').validator({
"input[name=card-number]": {
validations: {
lunCheck: function(value) {/* isValidLuhn(value)*/ }
},
errorMessageSelector: ".card-name-errors"
} //etc.
}).on('submit', function(e) {
const validation = $(this).validate(); //validation plugin
if (validation.passed) {
$.post('/api/cards', $(this).serializeJSON());
} else {
e.preventDefault();
}
}).on('change', function() {
const validation = $(this).validate();
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
}).on('change', function(e) {
const validation = $(this).validate();
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
const creditCardNumber = this.elements['card-number'];
if (e.target === creditCardNumber) {
$(this).find('.card-name-check-spinner').toggleClass('visible', true);
$.get(`/api/fraud-detector/cards/${creditCardNumber.value}`).then(()=> {
isSafeCardNumber = true;
}).fail(()=> {
$(this).find('input[type=submit]').prop('disabled', !validation.passed);
isSafeCardNumber = false;
}).always(() => {
$(this).find('.card-name-check-spinner').toggleClass('visible', false);
});
}
});
$('form').on('submit', function() {
$.post('/api/cards', $(this).serializeJSON());
});
let form = new CardForm({
number: '4242424242424242'
});
form.type /* visa */
form.isValid /* true */
form.isInvalid /* false */
//form.js
get type() {
const number = this.number;
if (number.match(VISA_REGEX)) {return "visa";}
else if (number.match(MASTERCARD_REGEX)) {return "mastercard";}
else if (number.match(AMEX_REGEX)) {return "amex";}
else if (number.match(DISCOVER_REGEX)) {return "discover";}
else if (number.match(DINERS_CLUB_REGEX)) {return "diners";}
else if (number.match(JCB_REGEX)) {return "jcb";}
else {return undefined;}
},
get isValid() {
const rules = this.rules;
return rules.reduce(function(currentValue, rule) {
return currentValue && rule.isFulfilled;
}, true);
},
get isInvalid() {
return !this.isValid;
}
Card Type: {{form.type}}
I want validation to happen every time a field changes
Create a new form for your computation every time
$('form').on('input', function() {
const form = new Form($(this).serialize());
update(form);
})
Let's verify with the server that this card isn't in our fraud database.
get isValid() {
const rules = this.rules;
return rules.reduce(function(currentValue, rule) {
return currentValue && rule.isFulfilled;
}, true);
},
Every validation rule is a promise
Compose Asynchronous And Synchronous Operations
flexible messaging
if (!number || number.length === 0) {
reject("can't be blank");
} else if (!_this.type) {
reject("not enough digits");
}
switch(_this.type) {
case 'diners':
case 'amex':
if (number.length === 15) {
resolve();
} else {
reject(`(${number.length}/15)`);
}
default:
if (number.length === 16) {
resolve();
} else {
reject(`(${number.length}/16)`);
}
}
{{#if rules.numberLongEnough.isRejected}}
{{rules.numberLongEnough.reason}}
{{/if}}
{{#if rules.numberPassesLuhnCheck.isRejected}}
{{rules.numberPassesLuhnCheck.reason}}
{{/if}}
Models dependencies of operations with then()
Length -> Luhn Check -> Fraud Check
What now?