/*!
* form
* https://github.com/Voliware/Template
* Licensed under the MIT license.
*/
/**
* Templates, serializes, and submits forms
* @extends Template
*/
class Form extends Template {
/**
* Constructor
* @param {object} [options]
* @param {boolean} [options.feedback=true] - whether to show feedback during submissions
* @param {string} [options.submitUrl] - the submitUrl or path to submit the form to
* @param {function} [options.submitRequest=null] - if set, ignores submitUrl and uses this function to submit data
* @param {number} [options.serializeMode=0] - the mode in which to serialize data
* @param {number} [options.checkboxMode=0] - the mode in which to serialize checkboxes
* @param {number} [options.formGroupManager=FormGroupManager] -
* @param {string[]} [options.excluded=[':disabled']] - exluded fields via css pseudo selectors
* @param {object} [options.validator] - validator setttings
* @param {string} [options.validator.api] - the validator api to use
* @param {object} [options.validator.options] - the validator options
* @param {object} [options.struct] - the template struct to build the form from, if using a template
* @param {string} [options.struct.$wrapper='form'] - the form element
* @param {string} [options.struct.$header='.form-header'] - the header selector
* @param {string} [options.struct.$body='.form-body'] - the body selector
* @param {string} [options.struct.$footer='.form-footer'] - the footer selector
* @param {string} [options.struct.$cancel='.form-cancel'] - the cancel button selector
* @param {string} [options.struct.$reset='.form-reset'] - the reset button selector
* @param {string} [options.struct.$submit='button[type="submit"]'] - the submit button selector
* @returns {Form}
*/
constructor(options){
var defaults = {
useTemplate : true,
submitUrl: "",
submitRequest : null,
serializeMode : FormSerializer.serializeMode.toString,
checkboxMode : FormSerializer.checkboxMode.number,
excluded : [':disabled'],
formGroupManager : FormGroupManager,
// feedback
feedback: true,
feedbackCloseable : true,
feedbackSuccess : 'Submission successful',
feedbackFail : 'Submission failed',
// css classes for each form component
struct: {
$wrapper: 'form',
$feedback: '.form-feedback',
$header: '.form-header',
$body: '.form-body',
$footer: '.form-footer',
$cancel: '.form-cancel',
$reset: '.form-reset',
$submit: 'button[type="submit"]'
},
validator: null
};
super($Util.opts(defaults, options, 'replace'));
var self = this;
// store serialized data
this._serializedData = {};
// alias
// this exists solely for Wizard !!
var $form = this.$wrapper.find('form');
this.$form = $form.length > 0
? $form
: this.$wrapper;
// components
this.formSerializer = new FormSerializer({
serializeMode : this.settings.serializeMode,
checkboxMode : this.settings.checkboxMode,
excluded : this.settings.excluded
});
this.validator = null;
this.feedback = null;
this.formGoupManager = new this.settings.formGroupManager({
$wrapper : this.$body
});
// handlers
// default submit handler
this.$form
.off('submit')
.on('submit', function(e){
e.preventDefault();
self.serializeForm()
._submit();
});
// cancel
this.$cancel.click(function(){
self.resetForm();
});
// reset
this.$reset.click(function(){
self.resetForm();
});
// set up validator
if(this.settings.validator)
this._setupValidator();
// set up feedback
if(this.settings.feedback)
this._setupFeedback();
return this;
}
// setup
/**
* Default form template
* @returns {Form}
* @private
*/
_useDefaultTemplate(){
var template =
'<form class="form">' +
'<div class="form-feedback"></div>' +
'<div class="form-header"></div>' +
'<div class="form-body"></div>' +
'<div class="form-footer">' +
'<button type="submit" class="form-submit">Submit</button>' +
'<button type="button" class="form-reset">Reset</button>' +
'<button type="button" class="form-cancel">Cancel</button>' +
'</div>' +
'</form>';
this._useTemplate($(template));
return this;
}
/**
* Attaches a validator to the form
* @returns {Form}
* @private
*/
_setupValidator(){
var v = this.settings.validator;
switch(v.api){
case 'formValidation':
Form.validators.formValidation.setup(this, this.$form, v.options);
break;
case 'formValidationBootstrap4':
Form.validators.formValidationBootstrap4.setup(this, this.$form, v.options);
break;
}
return this;
}
/**
* Setup the feedback
* @returns {Form}
* @private
*/
_setupFeedback(){
this.feedback = new Feedback({
closeButton : this.settings.feedbackCloseable
});
if(!this.$feedback.length){
this.$feedback = $('<div class="form-feedback"></div>');
this.$form.prepend(this.$feedback);
}
this.$feedback.html(this.feedback.$wrapper);
return this;
}
/**
* Prepare the form with a loading message
* @returns {Form}
* @private
*/
_prepare(){
this.toggleForm(false);
this.feedback.show();
this.feedback.setFeedback('processing', 'Getting data...');
return this;
}
// form builder
/**
* Build inputs from cols
* @param {object|object[]} data - data for a form input
* @returns {Form}
*/
build(data){
this.formGoupManager.empty().build(data);
return this;
}
// ready
/**
* Set the form to ready by hiding
* feedback and showing the form components
* @returns {Form}
* @private
*/
_ready(){
this.feedback.slideUp();
this.slideToggleForm(true);
return this;
}
// submit
/**
* Submits the form
* @returns {jQuery}
* @private
*/
_submit(){
var self = this;
this.trigger('beforeSubmit', this);
if(this.feedback)
this.feedback.setFeedback('processing', 'Processing...');
return this._doSubmit()
.done(function(data){
self._done(data);
})
.fail(function(err){
self._fail(err);
})
.always(function(){
self._always();
});
}
/**
* Actual submit function
* @returns {jQuery}
* @private
*/
_doSubmit(){
if(this.settings.submitRequest)
return this.settings.submitRequest(this._serializedData);
else
return $.post(this.settings.submitUrl, this._serializedData);
}
// submit handlers
/**
* Form submission success handler
* @param {object} data
* @returns {Form}
* @private
*/
_done(data){
if(this.feedback)
this.feedback.setFeedback('success', this.settings.feedbackSuccess);
this.trigger('done', data);
return this;
}
/**
* Form submission fail handler
* @param {object} err
* @returns {Form}
* @private
*/
_fail(err){
if(this.feedback)
this.feedback.setFeedback('danger', this.settings.feedbackFail);
this.trigger('fail', err);
return this;
}
/**
* Form submission always handler
* @returns {Form}
* @private
*/
_always(){
this.toggleButtons(true);
this.trigger('always');
return this;
}
// data
/**
* Get form data from the backend
* @returns {jQuery}
* @private
*/
_getFormData(){
return $.Deferred().resolve().promise();
}
// public
/**
* Toggle the button states
* @param {boolean} state
* @returns {Form}
*/
toggleButtons(state){
this.$cancel.prop('disabled', !state);
this.$reset.prop('disabled', !state);
this.$submit.prop('disabled', !state).toggleClass('disabled', !state);
return this;
}
/**
* Lock the submit button for some amount of ms
* @param {number} ms - time to lock in milliseconds
*/
lockSubmit(ms){
var self = this;
var html = this.$submit.html();
this.$submit.prop('disabled', true);
setTimeout(function() {
self.$submit.prop('disabled', false );
self.$submit.html(html);
}, ms);
var c = 0;
var timer = setInterval(setButtonHtml, 1000);
setButtonHtml();
/**
* Set the button html to the time left on the lock
*/
function setButtonHtml(){
if(c >= ms){
clearInterval(timer);
}
else{
var time = Math.floor((ms - c) / 1000);
// don't show 0
time = time || 1;
var _html = html + " | " + time;
self.$submit.html(_html);
c += 1000;
}
}
}
/**
* Toggle the form body
* @param {boolean} state
* @returns {Form}
*/
toggleForm(state){
this.$body.toggle(state);
this.$footer.toggle(state);
return this;
}
/**
* Slide toggle the form body
* @param {boolean} state
* @returns {Form}
*/
slideToggleForm(state){
this.$body.slideToggleState(state);
this.$footer.slideToggleState(state);
return this;
}
/**
* Populate form fields
* @param {object} data - collection of properties whos
* key match an input or select name, and
* whos value is appropriate for that field
* @returns {Form}
*/
populateForm(data){
this._cacheData(data);
this._processData(data);
this.$form.populateChildren(this._processedData);
return this;
}
/**
* Public function to serialize the form,
* as jQuery uses serialize already
* @returns {Form}
*/
serializeForm(){
this._serializedData = this.formSerializer.serialize(this.$form);
return this;
}
/**
* Append serialized data
* @param {...} an object of data or k/v pair of data
* @returns {Form}
*/
appendSerializedData(){
if(arguments.length > 1){
this._serializedData[arguments[0]] = arguments[1];
}
else {
$.extend(true, this._serializedData, arguments[0]);
}
return this;
}
/**
* Reset the form, using populated data
* or setting to default values
* @returns {Form}
*/
resetForm(){
if(!$.isEmptyObject(this._processedData))
this.$form.populateChildren(this._processedData);
else
this.$form[0].reset();
if(this.feedback)
this.feedback.slideUp();
this.toggleButtons(true);
// todo: implement for alternative validators
if(this.validator){
switch(this.settings.validator.api) {
case 'formValidation':
this.validator.resetForm();
break;
}
}
return this;
}
/**
* Validate the form
* @returns {boolean}
*/
validate(){
var isValid = false;
if(this.validator){
// todo: implement for alternative validators
switch(this.settings.validator.api){
case 'formValidation':
this.validator.resetForm();
this.validator.validateContainer(this.$form);
isValid = this.validator.isValidContainer(this.$form);
break;
}
}
return isValid;
}
// initializers
/**
* Remove all data from the form and reset it
* @returns {Form}
*/
clean(){
this._cachedData = {};
this.resetForm();
this.toggleForm(true);
return this;
}
/**
* Initialize as a clean form with
* default values from the DOM
* @returns {Form}
*/
initialize(){
this.clean();
return this;
}
}
Form.validators = {
/**
* formValidation api
*/
formValidation : {
api : 'formValidation',
options : {
framework: 'bootstrap',
excluded: [':disabled', ':hidden', ':not(:visible)'],
icon: {
valid: 'glyphicon glyphicon-ok',
invalid: 'glyphicon glyphicon-remove',
validating: 'glyphicon glyphicon-refresh'
}
},
/**
* formValidation setup
* @param {Form} form
* @param {jQuery} $form
* @param {object} options
*/
setup : function(form, $form, options){
$form.off('submit');
// allows re-creation of the Form
if($form.data('formValidation'))
$form.data('formValidation').destroy();
$form.formValidation(options)
.on('success.form.fv', function(e) {
e.preventDefault();
form.toggleButtons(false);
form.serializeForm()
._submit();
});
form.validator = $form.data('formValidation');
}
}
};
/**
* formValidation api bootstrap 4
*/
Form.validators.formValidationBootstrap4 = {
api : 'formValidation',
options : {
framework: 'bootstrap4',
excluded: [':disabled', ':hidden', ':not(:visible)'],
icon: {
valid: 'fa fa-check',
invalid: 'fa fa-times',
validating: 'fa fa-refresh'
}
},
setup : Form.validators.formValidation
};