asotog
9/13/2013 - 2:39 PM

Screen Flow widget for forms

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']
});