Screen Flow widget for forms
YUI.add('rt-form-flow', function (Y) {
Y.RTFormFlow = Y.Base.create('rt-form-flow', Y.Widget, [], {
box: null,
backBtn: null,
stepBx: null,
fieldBx: null,
subFrm: null,
continueBtn: null,
navBx: null,
initializer: function () {
this.initProperties();
if (this.get('initialize')) {
this.initNav();
var firstStep = this.box.all(this.stepBx).item(this.get('stepIndex'));
var firstSubForm = firstStep.all(this.subFrm).item(0);
firstStep.addClass('current');
firstSubForm.addClass('current');
firstSubForm.setStyle('display', 'block');
}
},
initNav: function () {
this.box.one(this.navBx).all('li').item(this.get('stepIndex')).addClass('current');
},
/**
* Sets up arguments to local properties
*
*/
initProperties: function () {
this.box = this.get('contentBox');
this.stepBx = this.get('stepBx');
this.subFrm = this.get('subFrm');
this.continueBtn = this.get('continueBtn');
this.fieldBx = this.get('fieldBx');
this.navBx = this.get('navBx');
this.backBtn = this.get('backBtn');
},
/**
* Attaches required events
*
*/
bindUI: function () {
var me = this;
this.box.delegate('click', function (e) {
e.preventDefault();
var subForm = e.currentTarget.get('parentNode');
me.validateSubForm(subForm, function(isValid) {
if (isValid) {
me.nextSubForm(subForm);
}
});
}, this.continueBtn);
this.box.delegate('click', function (e) {
e.preventDefault();
var subForm = e.currentTarget.get('parentNode');
me.prevSubForm(subForm);
}, this.backBtn);
this.box.one(this.navBx).delegate('click', function (e) {
e.preventDefault();
var stepIndex = me.box.one(me.navBx).all('a').indexOf(e.currentTarget);
if (!e.currentTarget.get('parentNode').hasClass('current')) {
me.selectStep(stepIndex);
}
}, 'a');
this.after('stepIndexChange', this.syncUI, this);
this.after('stepFilledIndexChange', this.syncUI, this);
},
/**
* Refreshes some elements of the UI
*
*/
syncUI: function () {
/* update nav */
var navItems = this.box.one(this.navBx).all('li');
navItems.removeClass('current');
if (navItems.item(this.get('stepIndex'))) {
navItems.item(this.get('stepIndex')).addClass('current');
}
if (navItems.item(this.get('stepFilledIndex'))) {
navItems.item(this.get('stepFilledIndex')).addClass('filled');
}
},
/**
* Selects an specific step based on the index passed, validating if current form is valid and if selected step is a valid one
*
* @param int Index
*/
selectStep: function (index) {
var me = this;
var anchorsList = this.box.one(this.navBx).all('a');
var selectedStep = anchorsList.item(index).get('parentNode');
this.validateSubForm(this.box.one(this.subFrm + '.current'), function(isValid) {
if (isValid || (anchorsList.size() - 1 == me.get('stepIndex'))) {
/* if selected step was already filled go to it */
if (selectedStep.hasClass('filled')) {
me.goToStep(index);
return;
}
/* verify if the previous step of the selcted step was filled to go to the selected one */
if (index > 0) {
var prevStep = me.box.one(me.navBx).all('a').item(index - 1).get('parentNode');
if (prevStep.hasClass('filled')) {
me.goToStep(index);
}
}
}
});
},
/**
* Goes directly to specific step and pointing first form of the step
*
* @param int Index
*/
goToStep: function (index) {
this.set('stepIndex', index);
this.box.all(this.stepBx).removeClass('current');
this.box.all(this.subFrm).setStyle('display', '');
var step = this.box.all(this.stepBx).item(index);
var subForm = step.all(this.subFrm).item(0);
step.addClass('current');
subForm.addClass('current');
subForm.setStyle('display', 'block');
},
/**
* Go to previous form based on the current form element provided
*
* @param Node SubForm
*/
prevSubForm: function (subForm) {
var step = subForm.get('parentNode');
var index = step.all(this.subFrm).indexOf(subForm);
if (index > 0) {
this.removeCurrent(subForm);
this.setCurrent(step.all(this.subFrm).item(index - 1));
} else {
var stepsForm = step.get('parentNode');
var stepIndex = stepsForm.all(this.stepBx).indexOf(step);
if (stepIndex > 0) {
step.removeClass('current');
this.removeCurrent(subForm);
var prevStep = stepsForm.all(this.stepBx).item(stepIndex - 1);
var prevStepForms = prevStep.all(this.subFrm);
prevStep.addClass('current');
this.setCurrent(prevStepForms.item(prevStepForms.size() - 1));
this.set('stepIndex', this.get('stepIndex') - 1);
}
}
},
/**
* Go to next form based on the current form element provided
*
* @param Node SubForm
*/
nextSubForm: function (subForm) {
var me = this;
var nextSubFormFound = false;
var allCurrentSubForms = subForm.get('parentNode').all(this.subFrm);
var currentFormIndex = allCurrentSubForms.indexOf(subForm);
nextSubFormFound = (allCurrentSubForms.size() > (currentFormIndex + 1));
this.removeCurrent(subForm);
if (!nextSubFormFound) {
var nextStep = me.get('stepIndex') + 1;
me.set('stepFilledIndex', (me.get('stepFilledIndex') < me.get('stepIndex')) ? (me.get('stepFilledIndex') + 1) : me.get('stepFilledIndex'));
this.goToStep(nextStep);
} else {
this.setCurrent(allCurrentSubForms.item(currentFormIndex + 1));
}
},
removeCurrent: function (node) {
node.removeClass('current');
node.setStyle('display', '');
},
setCurrent: function (node) {
node.addClass('current');
node.setStyle('display', 'block');
},
/**
* Validates if the current subform is a valid one
*
*
* @param Node SubForm
*/
validateSubForm: function (subform, callback) {
/* TODO: Create a different object for validation maybe, to handle the rivet common portlet js validators */
var fields = subform.all(this.fieldBx);
var onFormValidation = this.get('onSubFormValidation');
onFormValidation(fields, callback);
}
}, {
ATTRS: {
/* selectors */
continueBtn: {
value: '.continue'
},
stepBx: {
value: '.step'
},
fieldBx: {
value: '.rt-field-wrapper.rt-field-required'
},
subFrm: {
value: '.subform'
},
navBx: {
value: '.navigation'
},
backBtn: {
value: '.back-button'
},
/* config */
initialize: {
value: true
},
/* sets current index */
stepIndex: {
value: 0
},
stepFilledIndex: {
value: -1
},
/**
* Overwrite this to provide fields validation
*
*/
onSubFormValidation: {
value: function(fields, callback) {
/* true if fields valid */
callback(true);
}
}
}
});
}, '1.0', {
requires: ['base-build', 'widget']
});