/**
 * jFormer is the steward of the form. Holds base functions which are not specific to any page, section, or component.
 * jFormer is inialized on top of the existing HTML and handles validation, tool tip management, dependencies, instances, triggers, pages, and form submission.
 *
 * @author Kirk Ouimet <kirk@kirkouimet.com>
 * @author Seth Jensen <seth@sethjdesign.com>
 * @version .5
 */
JFormer = Class.extend({
    init: function(formId, options) {
        // Update the options object
        this.options = $.extend({
            pageNavigator: false,
            saveState: false,
            splashPage: false,
            onSubmitStart: function() { return true; },
            onSubmitFinish: function() { return true; },
            alertsEnabled: true,
            submitButtonText: 'Submit'
        }, options.options || {});

        // Class variables
        this.id = formId;
        this.form = $(['#',this.id].join(''));
        this.formData = {};
        this.jFormPageWrapper = $(['#', this.id , ' .jFormPageWrapper'].join(''));
        this.jFormPageScroller = $(['#', this.id , ' .jFormPageScroller'].join(''));
        this.jFormPageNavigator = null;
        this.jFormPages = {};
        this.currentJFormPage = null;
        this.jFormPageIdArray = [];
        this.currentJFormPageIdArrayIndex = null;
        this.jValidator = new JValidator();

        // Stats
        this.initializationTime = (new Date().getTime()) / 1000;
        this.durationInSeconds = 0;
        this.jFormComponentCount = 0;

        // Controls
        this.controlNextLi = this.form.find('.jFormerControl .nextLi');
        this.controlNextButton = this.controlNextLi.find('.nextButton');
        this.controlPreviousLi = this.form.find('.jFormerControl .previousLi');
        this.controlPreviousButton = this.controlPreviousLi.find('.previousButton');

        // Save states
        this.saveIntervalSetTimeoutId = null;

        // Initialize all of the pages
        this.initPages(options.jFormPages);

        // Add a splash page if enabled
        if(this.options.splashPage !== false || this.options.saveState !== false) {
            if(this.options.splashPage == false) {
                this.options.splashPage = {};
            }
            this.addSplashPage();
        }
        // Set the current page
        else {
            this.currentJFormPageIdArrayIndex = 0;
            this.currentJFormPage = this.jFormPages[this.jFormPageIdArray[0]];
            this.currentJFormPage.active = true;
            this.currentJFormPage.startTime = (new Date().getTime() / 1000 );
            // Add the page navigator
            if(this.options.pageNavigator !== false) {
                this.addPageNavigator();
            }
        }

        // Setup the page scroller - mainly CSS changes to width and height
        this.setupPageScroller();



        // Hide all inactive pages
        this.hideInactivePages();

        // Setup the control buttons
        this.setupControl();

        // Add a submit button listener
        this.addSubmitListener();

        // Add enter key listener
        this.addEnterKeyListener();

        // Analytics
        this.recordAnalytics();
    },

    initPages: function(jFormPages) {
        var self = this
        var each = $.each;

        each(jFormPages, function(jFormPageKey, jFormPageValue) {
            var jFormPage = new JFormPage(self, jFormPageKey, jFormPageValue.options);
            each(jFormPageValue.jFormSections, function(jFormSectionKey, jFormSectionValue) {
                var jFormSection = new JFormSection(jFormPage, jFormSectionKey, jFormSectionValue.options);
                each(jFormSectionValue.jFormComponents, function(jFormComponentKey, jFormComponentValue) {
                    self.jFormComponentCount = self.jFormComponentCount + 1;
                    jFormSection.addComponent(new window[jFormComponentValue.type](jFormSection, jFormComponentKey, jFormComponentValue.type, jFormComponentValue.options));
                });
                jFormPage.addSection(jFormSection);
            });
            self.addPage(jFormPage);
        });
    },

    select: function(jFormComponentId) {
        var componentFound = false,
        component = null,
        each = $.each;
        each(this.jFormPages, function(pageKey, pageObject){
          each(pageObject.sections, function(sectionKey, sectionObject){
              each(sectionObject.components, function(componentKey, componentObject){
                    if (componentObject.id == jFormComponentId){
                        component = componentObject;
                        componentFound = true;
                    }
                    return !componentFound;
              });
              return !componentFound;
          });
          return !componentFound;
      });
      return component;
    },

    addSplashPage: function() {
        var self = this;

        // Setup the jFormPage for the splash page
        this.options.splashPage.jFormPage = new JFormPage(this, this.form.find('.jFormerSplashPage').attr('id'));
        this.options.splashPage.jFormPage.addSection(new JFormSection(this.options.splashPage.jFormPage, this.form.find('.jFormerSplashPage').attr('id') + '-section'));
        this.options.splashPage.jFormPage.page.width(this.form.width());
        this.options.splashPage.jFormPage.active = true;
        this.options.splashPage.jFormPage.startTime = (new Date().getTime() / 1000 );

        // Set the splash page as the current page
        this.currentJFormPage = this.options.splashPage.jFormPage;

        // Set the height of the page wrapper to the height of the splash page
        this.jFormPageWrapper.height(this.options.splashPage.jFormPage.page.outerHeight());

        // If they have a custom button
        if(this.options.splashPage.customButtonId) {
            this.options.splashPage.controlSplashLi = this.form.find('#'+this.options.splashPage.customButtonId);
            this.options.splashPage.controlSplashButton = this.form.find('#'+this.options.splashPage.customButtonId);
        }
        // Use the native control buttons
        else {
            this.options.splashPage.controlSplashLi = this.form.find('.splashLi');
            this.options.splashPage.controlSplashButton = this.form.find('.splashButton');
        }

        // Hide the other native controls
        this.setupControl();

        // Handle save state options on the splash page
        if(this.options.saveState !== false) {
            self.addSaveStateToSplashPage();
        }
        // If there is no save state, just setup the button to start the form
        else {
            this.options.splashPage.controlSplashButton.bind('click', function(event) {
                event.preventDefault();
                self.beginFormFromSplashPage(false);
            });
        }
    },

    beginFormFromSplashPage: function(initSaveState, loadForm) {
        var self = this;

        // Add the page navigator
        if(this.options.pageNavigator !== false) {
            this.addPageNavigator();
        }

        // Mark the splash page as inactive
        self.options.splashPage.jFormPage.active = false;

        if(!loadForm){
        // Set the current page index
        self.currentJFormPageIdArrayIndex = 0;

        // Scroll to the new page, hide the old page when it is finished
        self.jFormPages[self.jFormPageIdArray[0]].scrollTo({onAfter: function() { self.options.splashPage.jFormPage.hide(); }});
        }

        // Initialize the save state is set
        if(initSaveState) {
            self.initSaveState();
        }
    },

    addSaveStateToSplashPage: function() {
        var self = this;

        // Initialize the three save state components
        var sectionId = self.options.splashPage.jFormPage.id + '-section';
        $.each(this.options.saveState.components, function(jFormComponentId, jFormComponentOptions) {
            self.options.splashPage.jFormPage.sections[sectionId].addComponent(new window[jFormComponentOptions.type](self.options.splashPage.jFormPage.sections[sectionId], jFormComponentId, jFormComponentOptions.type, jFormComponentOptions.options));
        });

        // When they change the option from new to resume, alter the label and peform maintenance
        var formState = 'newForm'; // Default value
        var saveStateJFormComponents = this.options.splashPage.jFormPage.sections[sectionId].components;
        saveStateJFormComponents.saveStateStatus.component.find('input:option').bind('click', { context: this }, function(event) {
            // Remove any failure notices
            self.form.find('.jFormerFailureNotice').remove();

            formState = $(event.target).val();
            // Change the form to reflect building a new form
            if(formState == 'newForm') {
                saveStateJFormComponents.saveStatePassword.component.find('label').html('Create password: <span class="jFormComponentLabelRequiredStar"> *</span>');
                self.options.splashPage.controlSplashButton.text('Begin');
            }
            // Change the form to reflect resuming a form
            else if(formState == 'resumeForm') {
                saveStateJFormComponents.saveStatePassword.component.find('label').html('Form password: <span class="jFormComponentLabelRequiredStar"> *</span>');
                self.options.splashPage.controlSplashButton.text('Resume');
            }
        });

        // Add a special event listener to the splash page start button
        self.options.splashPage.controlSplashButton.bind('click', { context: this }, function(event) {
            event.preventDefault();

            // Remove any failure notice
            self.form.find('.jFormerFailureNotice').remove();

            var validateSaveStateIdentifier = saveStateJFormComponents.saveStateIdentifier.validate();
            var validateSaveStatePassword = saveStateJFormComponents.saveStatePassword.validate();
            if(validateSaveStateIdentifier && validateSaveStatePassword) {
                // Set the form button text
                if(formState == 'newForm') {
                    self.options.splashPage.controlSplashButton.text('Creating form...');
                }
                else {
                    self.options.splashPage.controlSplashButton.text('Loading form...');
                }

                $(event.target).attr('disabled', 'disabled');
                $.ajax({
                    url: self.form.attr('action'),
                    type: 'post',
                    data: {
                        'task': 'initializeSaveState',
                        'identifier': saveStateJFormComponents.saveStateIdentifier.getValue(),
                        'password': saveStateJFormComponents.saveStatePassword.getValue(),
                        'formState' : formState
                    },
                    dataType: 'json',
                    success: function(json) {
                        // If the form was successfully initialized
                        if(json.status == 'success'){
                            if(formState == 'newForm'){
                                self.beginFormFromSplashPage(true, false);
                            }
                            else if(formState == 'resumeForm') {
                                self.beginFormFromSplashPage(true, true);

                                // Set the duration from the form save state
                                self.durationInSeconds = json.response.meta.totalTime;

                                // Load the data from the save state
                                self.setData(json.response.form);

                                // Scroll to the active page, set in the form save state
                                if(self.jFormPages[json.response.meta.currentPage].active === false) {
                                    self.currentJFormPageIdArrayIndex = self.jFormPageIdArray.indexOf(json.response.meta.currentPage);
                                    self.jFormPages[json.response.meta.currentPage].scrollTo({onAfter: function() { self.options.splashPage.jFormPage.hide(); }});
                                }
                            }
                        }
                        // If the form already exists
                        else if(json.status == 'exists') {
                            // Set the form button text
                            if(formState == 'newForm') {
                                self.options.splashPage.controlSplashButton.text('Begin');
                            }
                            else {
                                self.options.splashPage.controlSplashButton.text('Resume');
                            }

                            if(json.response.failureNoticeHtml) {
                                self.form.find('.jFormerControl').append($('<li class="jFormerFailureNotice jFormComponentValidationFailed">'+json.response.failureNoticeHtml+'</li>'));
                                self.setupControl();
                            }
                        }
                        // If the request failed
                        else if(json.status == 'failure') {
                            $(event.target).removeAttr('disabled');
                            // Set the form button text
                            if(formState == 'newForm') {
                                self.options.splashPage.controlSplashButton.text('Begin');
                            }
                            else {
                                self.options.splashPage.controlSplashButton.text('Resume');
                            }

                            // Set the failure notice
                            if(json.response.failureNoticeHtml){
                                self.form.find('.jFormerControl').append($(['<li class="jFormerFailureNotice jFormComponentValidationFailed">',json.response.failureNoticeHtml,'</li>'].join('')));
                                self.setupControl();
                            }
                            // Execute any failure javascript
                            if(json.response.failureJs){
                                eval(json.response.failureJs);
                            }
                        }
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown){
                        self.showAlert('There was a problem initializing the form.');
                    }
                });
           }
           // If the save state form does not validate, focus on the first failed component
           else {
               self.options.splashPage.jFormPage.focusOnFirstFailedComponent();
           }
        });
    },

    addPageNavigator: function(){
        var self = this, pageCount = 1;

        var tabClass = 'jFormPageTabTop';
        self.jFormPageNavigator = $('<div class="jFormPageNavigator"></div');
        this.jFormPageWrapper.before(self.jFormPageNavigator);
        self.jFormPageNavigator.append('<ul></ul>');
        if(this.options.pageNavigator.position == 'right'){
            self.jFormPageNavigator.addClass('rightNav');
            tabClass = 'jFormPageTabRight';
        }
        $.each(this.jFormPages, function(jFormPageId, jFormPage) {
            var item = ['<li id=\'navigatePage',pageCount,'\' class=',tabClass,'>Page ',pageCount,' </li>'].join('');
            self.jFormPageNavigator.find('ul').append(item);
            var tab = $(['#navigatePage'+pageCount].join(''));
            if(pageCount != 1){
                tab.addClass('tabDisabled');
            }
            else {
                tab.addClass('tabUnlocked');
                tab.click(function(event) {
                    self.currentJFormPageIdArrayIndex = 0;
                    self.scrollToPage(jFormPageId);
                });
            }
            pageCount = pageCount + 1; // Account for the lack of an extra page
        });

    },

    addPage: function(jFormPage) {
        this.jFormPageIdArray.push(jFormPage.id);
        this.jFormPages[jFormPage.id] = jFormPage;
    },

    addEnterKeyListener: function() {
        var self = this;

        // Prevent the default submission on key down
        this.form.bind('keydown', {context:this}, function(event) {
            if(event.keyCode === 13 || event.charCode === 13) {
                if($(event.target).is('textarea')){
                    return;
                }
                event.preventDefault();
            }
        });

        this.form.bind('keyup', {context:this}, function(event) {
            // Get the current page, check to see if you are on the splash page
            var currentPage = self.getActivePage().page;

            // Listen for the enter key keycode
            if(event.keyCode === 13 || event.charCode === 13) {
                var target = $(event.target);
                // Do nothing if you are on a text area
                if(target.is('textarea')){
                    return;
                }

                // If you are on a button, press it
                if(target.is('button')){
                    event.preventDefault();
                    target.trigger('click').blur();
                }
                // If you are on an input that is a check box or radio button, select it
                else if(target.is(':input:checkbox')) {
                    event.preventDefault();
                    target.trigger('click');
                }
                // If the next input is a remember me button, just submit the form
                else if(currentPage.find(':input').eq(currentPage.find(':input').index(event.target) + 1).val() == 'remember') {
                    event.preventDefault();
                    target.blur();
                    self.controlNextButton.trigger('click');
                }
            }
        });
    },

    addSubmitListener: function(){
        var self = this;
        this.form.bind('submit', {context: this}, function(event) {
            event.preventDefault();
            self.submitEvent(event);
        });
    },

    initSaveState: function() {
        var self = this, interval = this.options.saveState.interval * 1000;
        if(this.options.saveState === null){
            return;
        }
        this.saveIntervalSetTimeoutId = setInterval(function(){
            self.showAlert('Saving...');
            self.saveSate();
        }, interval);
        return;
    },

    saveSate: function() {
        var self = this;
        var tempDurationInSeconds = this.durationInSeconds + this.getTimeActive();
        var formJson = {};
        formJson.meta = {};
        formJson.meta.totalTime = tempDurationInSeconds;
        formJson.meta.currentPage = this.getActivePage().id;
        formJson.form = this.getData();
        $.ajax({
            url: self.form.attr('action'),
            type: 'post',
            data: {
                'task': 'saveState',
                'formData': utility.jsonEncode(formJson)
            },
            dataType: 'json',
            success: function(json) {
                //self.form.find('.jFormerAlert').text('Form saved.')
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                if(textStatus != 'error'){
                    errorThrown = textStatus ? textStatus : 'unknown';
                }
                self.showAlert('There was an error saving your form, we\'ll try again : '+ errorThrown, 'error');
            }
        });
    },

    getData: function() {
        //console.log('getting data for form');
        var self = this;
        this.formData = {};
        $.each(this.jFormPages, function(key, page) {
            self.formData[key] = page.getData();
        });
        return this.formData;
    },

    setData: function(data) {
        var self = this;
        this.formData = data;
        $.each(data, function(key, page) {
            self.jFormPages[key].setData(page);
        });
        return this.formData;
    },

    setupPageScroller: function() {
        var self = this;

        // Find all of the pages
        var pages = this.form.find('.jFormPage');

        // Set the width of each page
        pages.css('width', self.jFormPageWrapper.width() - utility.getExtraWidth(pages) - utility.getExtraWidth(self.jFormPageWrapper));

        // Set the width of the scroller
        self.jFormPageScroller.css('width', $('.jFormPageWrapper').width() * (pages.length + 1));

        // Set the width of the wrapper
        self.jFormPageWrapper.height(self.getActivePage().page.outerHeight());
    },

    setupControl: function() {
        var self = this;
       // console.log(this.currentJFormPageIdArrayIndex);
        // Setup event listener for next button
        this.controlNextButton.unbind().click(function(event) {
            event.preventDefault();
            event['context'] = self;
            self.submitEvent(event);
        }).removeAttr('disabled');

        // Setup event listener for previous button
        this.controlPreviousButton.unbind().click(function(event) {
            event.preventDefault();

            // Be able to return to the splash page
            if(self.options.splashPage !== false && self.currentJFormPageIdArrayIndex === 0) {
                self.currentJFormPageIdArrayIndex = null;
                self.options.splashPage.jFormPage.scrollTo();
            }
            // Scroll to the previous page
            else {
                self.currentJFormPageIdArrayIndex = self.currentJFormPageIdArrayIndex - 1;
                self.scrollToPage(self.jFormPageIdArray[self.currentJFormPageIdArrayIndex]);
            }
        });

        // First page with more pages after, or splash page
        if(this.currentJFormPageIdArrayIndex === 0 && this.currentJFormPageIdArrayIndex != this.jFormPageIdArray.length - 1) {
            this.controlNextButton.html('Next');
            this.controlNextLi.show();
            this.controlPreviousLi.hide();
            this.controlPreviousButton.attr('disabled', 'disabled');
        }
        // Last page
        else if(self.currentJFormPageIdArrayIndex == this.jFormPageIdArray.length - 1) {
            this.controlNextButton.html(this.options.submitButtonText);
            this.controlNextLi.show();

            // First page is the last page
            if(self.currentJFormPageIdArrayIndex === 0) {
                // Hide the previous button
                this.controlPreviousLi.hide();
                this.controlPreviousButton.attr('disabled', '');
            }
            // There is a previous page
            else if(self.currentJFormPageIdArrayIndex > 0) {
                this.controlPreviousButton.removeAttr('disabled');
                this.controlPreviousLi.show();
            }
        }
        // Middle page with a previous and a next
        else {
            this.controlNextButton.html('Next');
            this.controlNextLi.show();
            this.controlPreviousButton.removeAttr('disabled');
            this.controlPreviousLi.show();
        }

        // Splash page
        if(this.options.splashPage !== false) {
            // If you are on the splash page
            if(this.options.splashPage.jFormPage.active) {
                this.options.splashPage.controlSplashLi.show();
                this.controlNextLi.hide();
                this.controlPreviousLi.hide();
                this.controlPreviousButton.attr('disabled', 'disabled');
            }
            // If you aren't on the splash page, don't show the splash button
            else {
                this.options.splashPage.controlSplashLi.hide();
            }

            // If you are on the first page
            if(this.currentJFormPageIdArrayIndex === 0  && this.options.saveState == false) {
                this.controlPreviousButton.removeAttr('disabled');
                this.controlPreviousLi.show();
            }
        }

        // Failure page
        if(this.form.find('.jFormerControl .startOver').length == 1){
            // Hide the other buttons
            this.controlNextLi.hide();
            this.controlPreviousLi.hide();

            // Bind an event listener to the start over button
            this.form.find('.jFormerControl .startOver').click(function(event){
                event.preventDefault();
                self.scrollToPage(self.jFormPageIdArray[0], {
                    onAfter: function(){
                        $(event.target).parent().remove();
                        self.form.find('.failurePage').remove();
                        self.setupControl();
                    }
                });
            });
        }
    },

    scrollToPage: function(jFormPageId, options) {
        var self = this;
        var oldJFormPage = this.getActivePage();
        oldJFormPage.durationActiveInSeconds = oldJFormPage.durationActiveInSeconds + oldJFormPage.getTimeActive();

        // Show every page so you can see them as you scroll through
        $.each(this.jFormPages, function(jFormPageKey, jFormPage) {
            jFormPage.show();
            jFormPage.active = false;
        });

        // Set the current page to the splash page
        if(self.options.splashPage !== false && jFormPageId == self.options.splashPage.jFormPage.id) {
            self.currentJFormPage = self.options.splashPage.jFormPage;
            self.currentJFormPage.show();
        }
        // Set the current page to the new page
        else {
            self.currentJFormPage = self.jFormPages[jFormPageId];
        }

        // Mark the current page as active
        self.currentJFormPage.active = true;

        this.jFormPageWrapper.scrollTo(
            self.currentJFormPage.page,
            375,
            {
                axis:'xy',
                onAfter: function(){
                    if($(self.pageWrapper).queue('fx').length <= 1 ) {
                        self.hideInactivePages(self.getActivePage());
                    }
                    self.updateNavigation();
                    self.adjustHeight();
                    self.currentJFormPage.startTime = (new Date().getTime()/1000);

                    // Run any special functions
                    if(options) {
                        if(options.onAfter) {
                            options.onAfter();
                        }
                    }

                    // Setup the controls
                    self.setupControl();
                }
            }
        );

       this.scrollToTop();

        // Scroll to the top of the page

    },

    scrollToTop: function() {
        var top;
        if(utility.isSet(window.scrollY)) {
            top = window.scrollY;
        }
        else { // IE FTL
            top = document.documentElement.scrollTop;
        }
        if(top > 0) {
            $(document).scrollTo(this.form, 500, {
                offset: {
                    top: -10
                }
            });
        }

    },

    getActivePage: function() {
        // if active page has not been set
        return this.currentJFormPage;
    },

    getTimeActive: function(){
        var currentTotal = 0;
        $.each(this.jFormPages, function(key, page){
           currentTotal = currentTotal + page.durationActiveInSeconds;
        });
        currentTotal = currentTotal + this.getActivePage().getTimeActive();
        return currentTotal;
    },

    hideInactivePages: function(){
        $.each(this.jFormPages, function(jFormPageKey, jFormPage){
            jFormPage.hide();
        });
    },

    submitEvent: function(event) {
        var self = this;

        // Stop the event no matter what
        event.stopPropagation();
        event.preventDefault();

        // Remove any failure notices
        self.form.find('.jFormerControl .jFormerFailureNotice').remove();
        self.form.find('.jFormerFailure').remove();

        // Run a custom function at beginning of the form submission
        var onSubmitStartResult;
        if(typeof(self.options.onSubmitStart) != 'function') {
            onSubmitStartResult = eval(self.options.onSubmitStart);
        }
        else {
            onSubmitStartResult = self.options.onSubmitStart();
        }

        // Validate the current page if you are not the last page
        var clientSideValidationPassed = false;
        if(self.currentJFormPageIdArrayIndex < self.jFormPageIdArray.length - 1) {
            //console.log('Validating single page.');
            clientSideValidationPassed = self.getActivePage().validate();
        }
        else {
            //console.log('Validating whole form.');
            clientSideValidationPassed = self.validateAll();
        }

        // Run any custom functions at the end of the validation
        var onSubmitFinishResult = self.options.onSubmitFinish();

        // If the custom finish function returns false, do not submit the form
        if(onSubmitFinishResult) {
            // Last page, submit the form
            if(clientSideValidationPassed && self.currentJFormPageIdArrayIndex == self.jFormPageIdArray.length - 1) {
                self.submitForm(event);
            }
            // Not lost page, scroll to the next page
            else if(clientSideValidationPassed && self.currentJFormPageIdArrayIndex < self.jFormPageIdArray.length - 1) {
                self.currentJFormPageIdArrayIndex = self.currentJFormPageIdArrayIndex + 1;
                self.scrollToPage(self.jFormPageIdArray[self.currentJFormPageIdArrayIndex]);
            }
        }
    },

    updateNavigation: function(){
        var self = this, pageCount, pageIndex;
        for(var i = 1; i <= this.currentJFormPageIdArrayIndex + 1; i++) {
            pageCount = i;
            var tab = $('#navigatePage'+pageCount);
            if(tab.hasClass('tabDisabled')){
                //console.log(pageIndex);
                tab.removeClass('tabDisabled').addClass('tabUnlocked');
                tab.click(function(event) {
                    pageIndex = $(event.target).attr('id').match(/[0-9]+$/)
                    pageIndex = parseInt(pageIndex) - 1;
                    self.scrollToPage(self.jFormPageIdArray[pageIndex]);
                    self.currentJFormPageIdArrayIndex = pageIndex;
                });
            }
        }
    },

    validateAll: function(){
        var self = this;
        var validationPassed = true;
        $.each(this.jFormPages, function(jFormPageKey, jFormPage) {
            if(!jFormPage.validate()) {
                validationPassed = false;
                return false; // Break out of the .each
            }
        });
        return validationPassed;
    },

    adjustHeight: function(){
        this.jFormPageWrapper.animate( {
            'height' : this.getActivePage().page.outerHeight()
        }, 250 );
    },

    submitForm: function(event) {
        //console.log(utility.jsonEncode(this.getData()));

        // Use a temporary form targeted to the iframe to submit the results
        var formClone = this.form.clone(false).attr('id', $(this.form).attr('id')+'-clone').html('').hide().insertAfter($(this.form)); // Kage bunshin no jutsu!

        // Wrap all of the form responses into an object based on the component jFormComponentType
        var formData = $(document.createElement('input')).attr('name', 'jFormer').attr('value', encodeURI(utility.jsonEncode(this.getData()))).appendTo(formClone); // Set all non-file values in one form object

        // Add any file components for submission
        $.each(this.form.find('input:file'), function(index, fileInput) {
            fileInput.appendTo(formClone);
        });
        //console.log(utility.jsonEncode(this.getData()));
        // Submit the form
        formClone.submit();
        formClone.remove(); // Ninja vanish!

        // Find the submit button and the submit response
        $(this.form).find('.jFormerControl .nextButton').text('Processing...').attr('disabled', 'disabled');

        this.checkFormSubmissionProgress();
    },

    checkFormSubmissionProgress: function() {
        var self = this;
        $.ajax({
            url:self.form.attr('action'),
            type:'post',
            data:{'task':'getFormStatus'},
            dataType: 'json' ,
            success: function(json){
                // Form is still being processed
                if(json.status == 'processing'){
                    // Have this reset after 3 dots
                    //$(self.form).find('.jFormerControl .nextButton').text($(self.form).find('.jFormerControl .nextButton').text()+'.');

                    // Check the form progress again in a second
                    setTimeout(function() {
                        self.checkFormSubmissionProgress();
                    }, 1000);

                }
                // Form failed processing
                else if(json.status == 'failure') {
                    $.each(json.response, function(jFormPageKey, jFormPageValues){
                        $.each(jFormPageValues, function(jFormSectionKey, jFormSectionValues){
                            $.each(jFormSectionValues, function(jFormComponentKey, jFormComponentErrors){
                                self.jFormPages[jFormPageKey].sections[jFormSectionKey].components[jFormComponentKey].handleServerValidationResponse(jFormComponentErrors);
                            });
                        });
                    });
                    self.getActivePage().focusOnFirstFailedComponent();
                }
                // Form passed processing
                else if(json.status == 'success'){
                    // Show a success page
                    if(json.response.successPageHtml){
                        // Stop saving the form
                        clearInterval(self.saveIntervalSetTimeoutId);

                        // Create the success page
                        var successPageDiv = $('<div class="jFormPage jFormPageSuccess">'+json.response.successPageHtml+'</div>');
                        successPageDiv.width(self.jFormPages[self.jFormPageIdArray[0]].page.width());
                        self.jFormPageScroller.append(successPageDiv);

                        // Hide the page navigator and controls
                        self.form.find('.jFormerControl').hide();
                        self.form.find('.jFormPageNavigator').hide();

                        // Scroll to the page
                        self.jFormPageWrapper.scrollTo(successPageDiv, 375, {axis:'xy'});
                        self.scrollToTop();
                    }
                    // Show a failure page that allows you to go back
                    else if(json.response.failurePageHtml){
                        // Create the failure page
                        var failurePageDiv = $('<div class="jFormPage jFormPageFailure">'+json.response.failurePageHtml+'</div>');
                        self.jFormPageScroller.append(failurePageDiv);

                        // Create a start over button
                        self.form.find('.jFormerControl').append($('<li class="startOver"><button class="startOverButton">Start Over</button></li>'));

                        // Scroll to the failure page and reset the controls
                        self.jFormPageWrapper.scrollTo(failurePageDiv, 375, {axis:'xy'});
                        self.scrollToTop();
                    }
                    // Show a failure notice on the same page
                    if(json.response.failureNoticeHtml){
                        self.form.find('.jFormerControl .jFormerFailureNotice').remove();
                        self.form.find('.jFormerControl').append('<li class="jFormerFailureNotice">'+json.response.failureNoticeHtml+'</li>');
                    }

                    // Show a large failure response on the same page
                    if(json.response.failureHtml){
                        self.form.find('.jFormerControl .jFormerFailure').remove();
                        self.form.find('.jFormerControl').after('<div class="jFormerFailure">'+json.response.failureHtml+'</div>');
                    }

                    // Evaluate any failure or successful javascript
                    if(json.response.successJs){
                        eval(json.response.successJs);
                    }
                    else if(json.response.failureJs){
                        eval(json.response.failureJs);
                    }

                    // Redirect the user
                    if(json.response.redirect){
                        self.form.find('.jFormerControl .nextButton').html('Redirecting...');
                        document.location = json.response.redirect;
                    }
                    self.setupControl();
                }
            },
            error: function(XMLHttpRequest, textStatus, errorThrown){
                if(textStatus != 'error'){
                    errorThrown = textStatus ? textStatus : 'unknown';
                }
                    self.showAlert('There was an error proccessing your form, please submit again : '+ errorThrown, 'error');
            }
        });
    },

    showAlert: function(message, jFormComponentType){
        if(!this.options.alertsEnabled){
            return;
        }

        if(!jFormComponentType) {
            jFormComponentType = '';
        }

        var alertWrapper = this.form.find('.jFormerAlertWrapper');
        var alertDiv = this.form.find('.jFormerAlert');

        alertDiv.addClass(jFormComponentType);
        alertDiv.text(message);

        // Show the message
        alertWrapper.slideDown('slow', function(){
            setTimeout(function(){
                alertWrapper.slideUp('slow', function() {
                    alertDiv.removeClass(jFormComponentType);
                });
            }, 1000);
        });
    },

    recordAnalytics: function() {
        var image = $('<img src="http://www.jformer.com/analytics/analytics.gif?pageCount='+this.jFormPageIdArray.length+'&componentCount='+this.jFormComponentCount+'&formId='+this.id+'" style="display: none;" />');
        this.form.append(image);
        image.remove();
    }
});/*
 * How to pass validation options in PHP?
 *
 * "validations" => array("username", array("maxLength" => "10), array("serverValidation" => array("url" => "http://www.kirkouimet.com/test.php", "postData" => "
 *
 * How will it look in JSON?
 *
 *
 * Validations can have one or more arguments
 *
 * username(value)
 * matches(value, inputIdToMatch)
 * maxLength(value, maxLength)
 * serverValidation(value, url, postData)
 *  Will do a request which will get an object that looks like this: {"status":"success/failure", "errorMessage":["Username is taken."]}
 *
 * How will it look on the component level?
 *
 * SingleLineText
 * validate: function() {
 *   var self = this;
 *   var value = this.getValue();
 *   var errorMessageArray = [];
 *
 *   $.each(validationOptions, function(validationFunction, validationOptions) {
 *     self.jValidator.validations[validationFunction](value, validationOptions));
 *   }
 *   this.jValidator
 * }
 *
 *
 *
 *
 */

/*
reformValidations: function() {
    this.validations = {"0":"username","maxLength":"10","serverValidation":{"url":"http:\/\/www.kirkouimet.com\/test.php","postData":{"task":"checkUsernameAvailability"}}};
    var reformedValidations = {};

    $.each(this.validations, function(validationFunction, validationOptions) {
        // Check to see if the name of the function is actually an array index
        if(validationFunction >= 0) {
            validationFunction = validationOptions;
            validationOptions = {};
        }

        // Check to see if the validation options is not an object
        if(typeof(validationOptions) != 'object') {
            var tempValue = validationOptions;
            validationOptions = {};
            validationOptions[validationFunction] = tempValue;
        }
    });

    validationOptions['value'] = this.getValue();
    reformedValidations[validationFunction] = validationOptions;

    this.validations = reformedValidations;
});

console.log(reformedValidationOptions);

console.log(parseInt(""))

*/



JValidator = Class.extend({
    init: function() {
        var self = this;
        this.validations = {
            'required': function(options) {
                var errorMessageArray = ['Required.'];
                return options.value != '' ? 'success' : errorMessageArray;
            },
            'blank': function(options) {
                var errorMessageArray = ['Must be blank.'];
                return $.trim(options.value).length == 0 ? 'success' : errorMessageArray;
            },
            'alpha': function(options) {
                var errorMessageArray = ['Must only contain letters.'];
                return options.value == '' || options.value.match(/^[A-Za-z]+$/i)  ? 'success' : errorMessageArray;
            },
            'alphaNumeric': function(options) {
                var errorMessageArray = ['Must only contain letters and numbers.'];
                return options.value == '' || options.value.match(/^[A-Za-z0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'numeric': function(options) {
                var errorMessageArray = ['Must only contain numbers.'];
                return options.value == '' || options.value.match(/^[0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'numericPositive': function(options) {
                var errorMessageArray = ['Must only contain numbers, and be greater than zero.'];
                return options.value == '' || options.value.match(/^[(?!-)0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'numericNegative': function(options) {
                var errorMessageArray = ['Must only contain numbers, and be less than zero.'];
                return options.value == '' || options.value.match(/^-[0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'numericZeroPositive': function(options) {
                var errorMessageArray = ['Must only contain numbers, and be zero or greater.'];
                return options.value == '' || options.value.match(/^[(?!-)0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'numericZeroNegative': function(options) {
                var errorMessageArray = ['Must only contain numbers, and be zero or less.'];
                return options.value == '' || options.value.match(/^0|-[0-9]+$/i)  ? 'success' : errorMessageArray;
            },
            'phone': function(options) {
                var errorMessageArray = ['Must be a valid phone number with an area code.'];
                return options.value == '' || options.value.match(/^(1[\-. ]?)?\(?[0-9]{3}\)?[\-. ]?[0-9]{3}[\-. ]?[0-9]{4}$/)  ? 'success' : errorMessageArray ;
            },
            'email': function(options) {
                var errorMessageArray = ['Must be a valid e-mail address.'];
                return options.value == '' || options.value.match(/^[A-Z0-9._%-\+]+@(?:[A-Z0-9\-]+\.)+[A-Z]{2,4}$/i)  ? 'success' : errorMessageArray;
            },
            'password': function(options) {
                var errorMessageArray = ['Password must be between 4 and 32 characters.'];
                return options.value == '' || options.value.match(/^.{4,32}$/)  ? 'success' : errorMessageArray;
            },
            'username': function(options) {
                var errorMessageArray = ['Must use 4 to 32 characters and start with a letter.'];
                return options.value == '' || options.value.match(/^[A-Za-z](?=[A-Za-z0-9_.]{3,31}$)[a-zA-Z0-9_]*\.?[a-zA-Z0-9_]*$/)  ? 'success' : errorMessageArray;
            },
            'date': function(options) {
                var errorMessageArray = ['Must be a date in the mm/dd/yyyy format.'];
                return options.value == '' || options.value.match(/^(0?[1-9]|1[012])[\- \/.](0?[1-9]|[12][0-9]|3[01])[\- \/.](19|20)[0-9]{2}$/)  ? 'success' : errorMessageArray;
            },
            'time': function(options) {
                var errorMessageArray = ['Must be a time in the hh:mm:ss tt format. ss and tt are optional.'];
                return options.value == '' || options.value.match(/^[0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i)  ? 'success' : errorMessageArray;
            },
            'dateTime': function(options) {
                var errorMessageArray = ['Must be a date in the mm/dd/yyyy hh:mm:ss tt format. ss and tt are optional.'];
                return options.value == '' || options.value.match(/^(0?[1-9]|1[012])[\- \/.](0?[1-9]|[12][0-9]|3[01])[\- \/.](19|20)?[0-9]{2} [0-2]?\d:[0-5]\d(:[0-5]\d)?( ?(a|p)m)?$/i)  ? 'success' : errorMessageArray;
            },
            'url': function(options) {
                var errorMessageArray = ['Must be a valid Internet address.'];
                return options.value == '' || options.value.match(/^((ht|f)tp(s)?:\/\/|www\.)?([\-A-Z0-9.]+)(\.[a-zA-Z]{2,4})(\/[\-A-Z0-9+&@#\/%=~_|!:,.;]*)?(\?[\-A-Z0-9+&@#\/%=~_|!:,.;]*)?$/i)  ? 'success' : errorMessageArray;
            },
            'ssn': function(options) {
                var errorMessageArray = ['Must be a valid US Social Security Number.'];
                return options.value == '' || options.value.match(/^\d{3}-?\d{2}-?\d{4}$/i)  ? 'success' : errorMessageArray;
            },
            'zip': function(options) {
                var errorMessageArray = ['Must be a valid US zip code.'];
                return options.value == '' || options.value.match(/^[0-9]{5}(?:-[0-9]{4})?$/)  ? 'success' : errorMessageArray;
            },
            'canadianZip': function(options) {
                var errorMessageArray = ['Must be a valid Canadian postal code.'];
                return options.value == '' || options.value.match(/^[ABCEGHJKLMNPRSTVXY][0-9][A-Z] [0-9][A-Z][0-9]$/)  ? 'success' : errorMessageArray;
            },
            'ukPostal' : function(options) {
                var errorMessageArray = ['Must be a valid UK postal code.'];
                return options.value == '' || options.value.match(/^[A-Z]{1,2}[0-9][A-Z0-9]? [0-9][ABD-HJLNP-UW-Z]{2}$/)  ? 'success' : errorMessageArray;
            },
            'money' : function(options) {
                var errorMessageArray = ['Must be a valid dollar value.'];
                return options.value == '' || options.value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/)  ? 'success' : errorMessageArray;
            },
            'moneyPositive' : function(options) {
                var errorMessageArray = ['Must be a valid positive dollar value.'];
                return options.value == '' || options.value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/) && RegExp.$5 > 0  ? 'success' : errorMessageArray;
            },
            'moneyZeroPositive' : function(options) {
                var errorMessageArray = ['Must be zero or a valid positive dollar value.'];
                return options.value == '' || options.value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/) && RegExp.$5 >= 0  ? 'success' : errorMessageArray;
            },
            'moneyNegative' : function(options) {
                var errorMessageArray = ['Must be a valid negative dollar value.'];
                return options.value == '' || options.value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/) && RegExp.$5 < 0  ? 'success' : errorMessageArray;
            },
            'moneyZeroNegative' : function(options) {
                var errorMessageArray = ['Must be a valid negative dollar value.'];
                return options.value == '' || options.value.match(/^((-?\$)|(\$-?)|(-))?((\d+(\.\d{2})?)|(\.\d{2}))$/) && RegExp.$5 <= 0  ? 'success' : errorMessageArray;
            },
            'matches': function(options) {
                var errorMessageArray = ['Must match the previous field.'];
                return options.value == $('#'+options.matches).val() ? 'success' : errorMessageArray;
            },
            'decimal': function(options) {
                // Can be negative and have a decimal value
                // Do not accept commas in value as the DB does not accept them
                var errorMessageArray = ['Must be a number without any commas.'];
                return options.value == '' || options.value.match(/^-?((\d+(\.\d+)?)|(\.\d+))$/) ? 'success' : errorMessageArray;
            },
            'decimalPositive': function(options) {
                // Must be positive and have a decimal value
                var errorMessageArray = ['Must be a positive number without any commas.'];
                var isDecimal = this.decimal(options);
                return options.value == '' ||  (isDecimal == 'success' && (parseFloat(options.value) > 0)) ? 'success' : errorMessageArray;
            },
            'decimalZeroOrPositive': function(options) {
                // Must be positive and have a decimal value
                var errorMessageArray = ['Must be zero or a positive number without any commas.'];
                var isDecimal = this.decimal(options);
                return options.value == '' || (isDecimal == 'success' && (parseFloat(options.value) >= 0)) ? 'success' : errorMessageArray;
            },
            'decimalNegative': function(options) {
                // Must be negative and have a decimal value
                var errorMessageArray = ['Must be a negative number without any commas.'];
                var isDecimal = this.decimal(options);
                return options.value == '' || (isDecimal == 'success' && (parseFloat(options.value) < 0)) ? 'success' : errorMessageArray;
            },
            'decimalZeroOrNegative': function(options) {
                // Must be negative and have a decimal value
                var errorMessageArray = ['Must be zero or a negative number without any commas.'];
                var isDecimal = self.validations.decimal({"value":options.value});
                return options.value == '' || (isDecimal == 'success' && (parseFloat(options.value) <= 0)) ? 'success' : errorMessageArray;
            },
            'integer': function(options) {
                var errorMessageArray = ['Must be a whole number.'];
                return options.value == '' || options.value.match(/^-?\d+$/) ? 'success' : errorMessageArray;
            },
            'integerPositive': function(options) {
                var errorMessageArray = ['Must be a positive whole number.'];
                var isInteger = this.integer(options);
                return options.value == '' || (isInteger == 'success' && (parseInt(options.value, 10) > 0)) ? 'success' : errorMessageArray;
            },
            'integerZeroOrPositive': function(options) {
                var errorMessageArray = ['Must be zero or a positive whole number.'];
                var isInteger = this.integer(options);
                return options.value == '' || (isInteger == 'success' && (parseInt(options.value, 10) >= 0)) ? 'success' : errorMessageArray;
            },
            'integerNegative': function(options) {
                var errorMessageArray = ['Must be a negative whole number.'];
                var isInteger = this.integer(options);
                return options.value == '' || (isInteger == 'success' && (parseInt(options.value, 10) < 0)) ? 'success' : errorMessageArray;
            },
            'integerZeroOrNegative': function(options) {
                var errorMessageArray = ['Must be zero or a negative whole number.'];
                var isInteger = this.integer(options);
                return options.value == '' || (isInteger == 'success' && (parseInt(options.value, 10) <= 0)) ? 'success' : errorMessageArray;
            },
            'length' : function(options) {
                var errorMessageArray = ['Must be exactly ' + options.length + ' characters long. The string is '+ options.value.length +' characters.'];
                return options.value == '' || options.value.length == options.length  ? 'success' : errorMessageArray;
            },
            'maxLength' : function(options) {
                var errorMessageArray = ['Must be less than ' + options.maxLength + ' characters long. The string is '+ options.value.length +' characters.'];
                return options.value == '' || options.value.length <= options.maxLength  ? 'success' : errorMessageArray;
            },
            'minLength' : function(options) {
                var errorMessageArray = ['Must be more than ' + options.minLength + ' characters long. The string is '+ options.value.length +' characters.'];
                return options.value == '' || options.value.length >= options.minLength  ? 'success' : errorMessageArray;
            },
            'teenager': function(options) {
                var errorMessageArray = 'You must be at least 13 years old to use this site.',
                birthday = new Date(options.value),
                now = new Date(),
                limit = new Date(now.getFullYear() - 13 , now.getMonth(), now.getDate()),
                timeDifference = (limit - birthday);
                return options.value == '' || timeDifference >= 0  ? 'success' : errorMessageArray;
            },
            'maxFloat': function(options) {
                //Value cannot have more digits then specified in maxFloat
                var errorMessageArray = 'Must be numberic and cannot have more than ' + options.maxFloat + ' decimal place(s).',
                maxFloatPattern = new RegExp('^-?((\\d+(\\.\\d{0,'+ options.maxFloat + '})?)|(\\.\\d{0,' + options + '}))$');
                return options.value == '' || options.value.match(maxFloatPattern) ? 'success' : errorMessageArray;
            },
           'isbn': function(options) {
                //Match an ISBN
                var errorMessageArray = ['Must be a valid ISBN and consist of either ten or thirteen characters.'];
                //For ISBN-10
                if(options.value.match(/^(?=.{13}$)\d{1,5}([\- ])\d{1,7}\1\d{1,6}\1(\d|X)$/)) {
                    errorMessageArray = 'sucess';
                }
                if(options.value.match(/^\d{9}(\d|X)$/)) {
                    errorMessageArray = 'sucess';
                }
                //For ISBN-13
                if(options.value.match(/^(?=.{17}$)\d{3}([\- ])\d{1,5}\1\d{1,7}\1\d{1,6}\1(\d|X)$/)) {
                    errorMessageArray = 'sucess';
                }
                if(options.value.match(/^\d{3}[\- ]\d{9}(\d|X)$/)) {
                    errorMessageArray = 'sucess';
                }
                //ISBN-13 without starting delimiter (Not a valid ISBN but less strict validation was requested)
                if(options.value.match(/^\d{12}(\d|X)$/)) {
                    errorMessageArray = 'sucess';
                }
                return errorMessageArray;
            },
            'postalZip': function(options) {
                var errorMessageArray = 'Must be a valid US Zip Code, Canadian postal code, or UK postal code.'
                return options.value == '' || this.zip(options) == 'success' || this.canadianPostal(options) == 'success' || this.ukPostal() == 'success' ? 'success' : errorMessageArray;
            },
            //MultipleChoice validations
            'requiredJFormComponentMultipleChoice': function(options) {
                var errorMessageArray = ['Required.'];
                return options.value.length > 0 ? 'success' : errorMessageArray;
            },
            'minOptionsJFormComponentMultipleChoice': function(options) {
                var errorMessageArray = ['You must select more than '+ options.minOptions +' options'];
                return options.value.length == 0 || options.value.length > options.minOptions ? 'success' : errorMessageArray;
            },
            'maxOptionsJFormComponentMultipleChoice': function(options) {
                var errorMessageArray = ['You may select up to '+ options.maxOptions +' options. You have selected '+ options.value.length + '.'];
                return options.value.length == 0 || options.value.length <= options.maxOptions ? 'success' : errorMessageArray;
            },

            //Name Validations
            'requiredJFormComponentName': function(options) {
                var errorMessageArray = [];
                if(options.value.firstName == '') {
                    errorMessageArray.push(['First name is required.']);
                }
                if(options.value.lastName == '') {
                    errorMessageArray.push(['Last name is required.']);
                }
                return errorMessageArray.length < 1 ? 'success' : errorMessageArray;
            },
            //Dropdown validations
            'requiredJFormComponentDropDown': function(options) {
                return this.required(options);
            },
            //Date validations
            'requiredJFormComponentDate': function(options) {
                return this.required(options);
            },
            'dateDate': function(options) {
                return this.required(options);
            },
            'teenagerDate': function(options) {
                return this.teenager(options);
            },
            // Server side validations!
            'serverSide': function(options) {
                if(options.value == '') {
                    return 'success'
                }

                // options: value, url, data
                var errorMessageArray = [];

                options.component.addClass('jFormComponentServerSideCheck');
                $.ajax({
                    url: options.url,
                    type: 'post',
                    data:{
                        'task': options.task,
                        'value': options.value
                    },
                    dataType: 'json' ,
                    cache: false,
                    async: false,
                    success: function(json) {
                        if(json.status != 'success') {
                            errorMessageArray = json.response;
                        }

                        options.component.removeClass('jFormComponentServerSideCheck');
                    },
                    error: function(XMLHttpRequest, textStatus, errorThrown){
                        if(textStatus != 'error'){
                            errorThrown = textStatus ? textStatus : 'Unknown error';
                        }
                        errorMessageArray = 'There was an error during server side validation: '+ errorThrown;
                        options.component.removeClass('jFormComponentServerSideCheck');
                    }
                });

                return errorMessageArray.length < 1 ? 'success' : errorMessageArray;
            }
        }

    },

    validate: function(validation, options) {
        return this.validations[validation](options);
    },

    addValidation: function(validationName, validationFunction) {
        this.validations[validationName] = validationFunction;
    }
});


/*
 *
 *


        isbnValue
    },



    emailIsAvailable: function(value, component) {
        var self = this;
        if(!this.isEmail(value) || value.blank()) {
            return true; // Lie to prevent doing an AJAX request
        }
        else {
            var url = '/php/classes/user.php';
            var params = {
                'task': 'checkIfEmailIsAvailable',
                'value': value
            };
            new Ajax.Request(url, {
                parameters: params,
                onSuccess: function(transport) {
                    var json = transport.responseText.evalJSON(true);
                    if(json.success) {
                        emailIsAvailable = true;
                    }
                    else {
                        self.validationFailed(component, 'E-mail address already in use.');
                        self.errorHandler(component, self.getErrors());
                    }
                }
            });
        }
    },








    */
/**
 * jFormPage handles all functions on the page level, including page validation.
 *
 */
JFormPage = Class.extend({
    init: function(jFormer, pageId, options) {
        this.options = $.extend({

            }, options || {});

        // Class variables
        this.jFormer = jFormer;
        this.id = pageId;
        this.page = $('#'+pageId);
        this.sections = {};
        this.formData = {};
        this.active = false;
        this.validationPassed = false;
        this.durationActiveInSeconds = 0;
    },

    addSection: function(section) {
        this.sections[section.id] = section;
        return this;
    },

    getData: function() {
        //console.log('getting data for page');
        var self = this;
        this.formData = {};
        $.each(this.sections, function(key, section) {
            self.formData[key] = section.getData();
        });
        return this.formData;
    },

    setData: function(data) {
        var self = this;
        this.formData = data;
        $.each(data, function(key, values) {
            self.sections[key].setData(values);
        });
        return this.formData;
    },

    getTimeActive: function(){
        var currentActiveTime =(new Date().getTime() / 1000) -  this.startTime ;
        return currentActiveTime;
    },

    validate: function() {
        var self = this;
        var each = $.each;

        self.validationPassed = true;
        each(this.sections, function(sectionKey, section) {
            each(section.components, function(componentKey, component) {
                each(component.instanceArray, function(instanceIndex, instance) {
                    if(instance.validate() === false) {
                        self.validationPassed = false;
                    }
                });
            });
        });
        if(!self.validationPassed) {
           this.focusOnFirstFailedComponent();
        }

        return self.validationPassed;
    },

    focusOnFirstFailedComponent: function() {
        var each = $.each,
        validationPassed = true;
        each(this.sections, function(sectionLabel, section){
            each(section.components, function(componentLabel, component){
                each(component.instanceArray, function(instanceLabel, instance){
                    if(!instance.validationPassed){

                        var offset = instance.component.position().top - 30,
                        top;
                        if(utility.isSet(window.scrollY)) {
                            top = window.scrollY;
                        }
                        else { // IE FTL
                            top = document.documentElement.scrollTop;
                        }
                        if(top < offset && top + $(window).height() > instance.component.position().top) {
                            instance.component.find(':input:first').focus();
                            //instance.highlight();
                        } else {
                            $.scrollTo(offset + 'px', 500, {
                                onAfter: function() {
                                    instance.component.find(':input:first').focus();
                                    //instance.highlight();
                                }
                            });
                        }
                        validationPassed = false;
                    }
                    return validationPassed;
                });
                return validationPassed;
            });
            return validationPassed;
        });
    },

    scrollTo: function(options) {
        this.jFormer.scrollToPage(this.id, options);
        return this;
    },

    show: function(){
        if(this.page.hasClass('jFormPageInactive')){
            this.page.removeClass('jFormPageInactive');
        }
    },

    hide:function() {
        if(!this.active){
            this.page.addClass('jFormPageInactive');
        }
    }

});/**
 * jFormSection handles all functions on the section level, including dependencies and instances. A section groups components.
 *
 */
JFormSection = Class.extend({
    init: function(parentPage, sectionId, options) {
        this.options = $.extend({
            dependencyDisplay: 'hidden',        // 'hidden' or 'locked' - Used to determine how the section is displayed when dependencies are not met
            dependencyConditionObject: null,       // {'jFormComponentId', 'value' or 'jFormComponentValidationPassed'} - Used to setup dependencies to the values or validation status of one or many components
            //      [[{'component1': 'Yes' } , {'component4': 'validates'}], {'component4': 'validates'}]
            //      Grouped dependencies in an array must all be met, or it will move to the next dependency set
            instancesAllowed: 1,                 // 0 or greater - 0 indicates unlimited, otherwise the number of allowed instances will be set to this number
            instanceAddText: 'Add Another',
            instanceRemoveText: 'Remove'
        }, options || {});


        // Class variables
        this.parentPage = parentPage;
        this.id = sectionId;
        this.section = $('#'+sectionId);
        this.components = {};
        this.formData = null;                       // Will be an object is there is just one instance, will be an array if there is more than one instance

        if(this.options.isInstance){
            this.instanceArray = null;
            this.clone = null;                  // clone of the original html.. only initiates if instances are turned on...

        } else { // do parentInstance functions
            this.clone = this.section.clone();
            this.instanceArray = [this];
            this.createInstanceButton();
            this.iterations = 1;
        }

    },

    addDependencyListeners: function() {
        var self = this;
        if(this.options.dependencyConditionObject) {
            this.toggleDependencyLock(true);
            $.each(this.options.dependencyConditionObject, function(key, condition) {
                if(key == 'and') {
                    $.each(condition, function(andKey, andCondition){
                        addDependencyListener($('#'+andKey));
                    });
                }
                else {
                    addDependencyListener($('#'+key));
                }
            });
        }

        function addDependencyListener(input) {
            input.bind('component:changed', function() {
                self.checkDependencies();
            });
            // Force check dependencies on keyup for
            if(input.is('input:text, textarea')) {
                input.bind('keyup', function(event) {
                    self.checkDependencies();
                });
            }
        }
    },

    toggleDependencyLock: function(hide) {
        // Hide or lock the dependency
        if(hide) {
            if(this.options.dependencyDisplay == 'hidden'){
                this.section.hide();
            }
            else {
                this.section.addClass('dependencyDisabled').find(':input').each(function(index, input){
                    input.disabled = true;
                });
            }
        }
        else {
            if(this.options.dependencyDisplay == 'hidden'){
                this.section.show();
            }
            else {
                this.section.removeClass('dependencyDisabled').find(':input').each(function(index, input){
                    input.disabled = false;
                });
            }
        }
        return this;
    },

    checkDependencies: function() {
        // Hide or show the section based on whether or not the dependency requirements are met
        var self = this,
        hide = true;
        $.each(this.options.dependencyConditionObject, function(key, condition) {
            if(key == 'and') {
                $.each(condition, function(andKey, andCondition) {
                    if((andCondition == 'validate' && this.components[andKey].validate()) || $('#'+andKey).val() == andCondition ){
                        hide = false;
                    }
                    else {
                        hide = true;
                        return false;
                    }
                });
                if(hide === false){
                    return false;
                }
            }
            else if((condition == 'validate' && this.components[key].validate()) || $('#'+key).val() == condition){
                hide = false;
                return false;
            }
            else {
                hide = true;
            }
        });
        this.toggleDependencyLock(hide);

        return this;
    },

    createInstanceButton:function() {
        var self =  this;

      if  (this.options.instancesAllowed != 1){
          var buttonId = this.id+'-addInstance',
          addButton = '<button id="'+buttonId+'" class="jFormSectionAddInstanceButton">' + this.options.instanceAddText + '</button>';
          this.section.after(addButton);
          this.parentPage.page.find('#'+buttonId).bind('click', function(event){
              event.preventDefault();
              self.addSectionInstance();
          });
      }
    },

    addSectionInstance: function() {

        var parent = this;
        if (this.instanceArray.length < this.options.instancesAllowed || this.options.instancesAllowed === 0) {
            this.iterations++;
            var instanceClone = this.clone.clone(),
            buttonId = this.id+'-removeInstance',
            removeButton = '<button id="'+buttonId+'" class="jFormSectionRemoveInstanceButton">' + this.options.instanceRemoveText + '</button>';
            $(instanceClone).append(removeButton);
                instanceClone.find('#'+buttonId).bind('click', function(event){
                var target = $(event.target);
                event.preventDefault();
                parent.instanceArray = $.map(parent.instanceArray, function(cloneId, index){
                   if (cloneId.section.attr('id') ==  target.parent().attr('id')){
                        cloneId = null;
                   }
                   return cloneId;
                });
                target.parent().remove();
                target.remove();
                if (parent.instanceArray.length <= parent.options.instancesAllowed || parent.options.instancesAllowed === 0) {
                    parent.parentPage.page.find('#'+parent.id+'-addInstance').show();
                }
                parent.relabelSectionInstances(parent.instanceArray);
            });

            this.parentPage.page.find('#'+this.id+'-addInstance').before(instanceClone);
            this.nameSectionInstance(instanceClone);
            var instanceObject = this.createSectionInstanceObject(instanceClone, this.options);
            this.instanceArray.push(instanceObject);
            this.relabelSectionInstances(this.instanceArray);
            if (this.instanceArray.length >= this.options.instancesAllowed && this.options.instancesAllowed !== 0) {
                this.parentPage.page.find('#'+this.id+'-addInstance').hide();
            }

        }
        return this;
    },

    removeInstance: function() {
        return this;
    },

    nameSectionInstance: function(component) {
        var self = this,
        ending = '';
        $(component).attr('id', $(component).attr('id')+ '-section'+this.iterations);
        $(component).find('*').each(function(key, child){
            if($(child).attr('id')){
                changeName(child, 'id');
            }
            if($(child).attr('for')){
                changeName(child, 'for');
            }
            if($(child).attr('name')){
                changeName(child, 'name');
            }
        });
        function changeName(child, attribute){
            ending = getEnding($(child).attr(attribute)) ;
                if(ending == ''){
                    $(child).attr(attribute, $(child).attr(attribute) +'-section'+self.iterations+ending);
                }else {
                    $(child).attr(attribute, $(child).attr(attribute).replace(ending, '-section'+self.iterations+ending));
                }
        }

        function getEnding(identifier){
            var ending = '';
            if(identifier.match(/(\-[A-Za-z0-9]+)&?/)){
                ending = identifier.match(/(\-[A-Za-z0-9]+)&?/)[1];
            } else {

            }
            return ending;
        }

        return component;
    },

    createSectionInstanceObject:function(instanceClone, options){
        options.isInstance = true;
        var self = this,
        instanceObject = new JFormSection(this.parentPage, this.id+'-section'+this.iterations, options);
        $.each(this.components, function(key, component){
           component.options.isInstance = false;
           var componentClone = new window[component.type](instanceObject, component.id+'-section'+self.iterations, component.type, component.options);
           instanceObject.addComponent(componentClone);
        });
        return instanceObject;
    },

    relabelSectionInstances:function(instanceArray){
        $.each(instanceArray, function(key, instance){
            if( key!== 0) {
                var count = key+1,
                label = instance.section.find('.jFormSectionTitle').children(':first');
                if(label.length > 0){
                    if (label.text().match(/(\([0-9]+\))$/)){
                        label.text(label.text().replace(/(\([0-9]+\))$/, '('+count+')'));
                    } else {
                        label.text(label.text() + ' ('+count+')');
                    }

                }
            }
       });
       this.parentPage.jFormer.adjustHeight();
    },

    addComponent: function(component) {
        this.components[component.id] = component;
        return this;
    },

    getData: function() {
        var self = this;
        if(this.instanceArray.length > 1) {
            this.formData = [];
            $.each(this.instanceArray, function(index, section) {
                var sectionHasData = false;
                var sectionData = {};
                $.each(section.components, function(key, component) {
                    key = key.replace(/-section[0-9]+/, '');
                    var tempData = component.getData();
                    if(!utility.empty(tempData)){
                        //console.log(tempData, utility.empty(tempData));
                        sectionHasData = true;
                    }
                    sectionData[key] = component.getData();
                });
                if(sectionHasData){
                    self.formData.push(sectionData);
                }

            });
        }
        else {
            this.formData = {};
            $.each(this.components, function(key, component) {
                self.formData[key] = component.getData();
            });
        }
        return this.formData;
    },

    setData: function(data) {
        var self = this;
        if ($.isArray(data)){
            $.each(data, function(index, instance){
               if(index !== 0 && self.instanceArray[index] == undefined){
                   self.addSectionInstance();
               }
               $.each(self.instanceArray[index].components, function(key, component){
                   key = key.replace(/-section[0-9]+/, '');
                   component.setData(instance[key]);
               });
            });
        } else {
            $.each(this.components, function(key, component) {
                component.setData(data[key]);
            });
        }
    }
});/**
 *  jFormComponent is the base class for all components in the form. all specific components extend off of this class
 *  Handles instances, dependencies and trigger bases
 *
 */
JFormComponent = Class.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this.options = $.extend({
            validationOptions: [],                // 'required', 'email', etc... - An array of validation keys used by this.validate() and jFormerValidator
            triggerFunction: null,              // set to a function name, is a function
            dependencyDisplay: 'hidden',        // 'hidden' or 'locked' - Used to determine how the section is displayed when dependencies are not met
            dependencyConditionObject: null,       // {'jFormComponentId', 'value' or 'jFormComponentValidationPassed'} - Used to setup dependencies to the values or validation status of one or many components
            //      [[{'component1': 'Yes' } , {'component4': 'validate'}], {'component4': 'validate'}]
            //      Grouped dependencies in an array must all be met, or it will move to the next dependency set
            instancesAllowed: 1,                // 0 or greater - 0 indicates unlimited, otherwise the number of allowed instances will be set to this number
            instanceAddText: 'Add Another',
            instanceRemoveText: 'Remove',
            tipTargetPosition: 'rightMiddle',   // 'rightMiddle' - Where the tooltip will be placed in relation to the component
            tipCornerPosition: 'leftTop',       // 'leftTop' - The corner of the tip that will point to the tip target position
            isInstance: false

        }, options || {});

        //console.count(jFormComponentType);
        // Class variables
        this.parentJFormSection = parentJFormSection;
        this.id = jFormComponentId;
        this.component = $('#'+jFormComponentId+'-wrapper');
        this.formData = null;                       // Will be an object is there is just one instance, will be an array if there is more than one instance
        this.type = jFormComponentType;                       // 'SingleLineText', 'TextArea', etc... - The component jFormComponentType
        this.errorMessageArray = [];            // Used to store error messages displayed in tips or appended to the description
        this.tip = null;
        this.tipDiv = this.component.find('#'+this.id+'-tip');
        this.tipTarget = null;                  // The ID of the element where the tip will be targeted
        this.validationPassed = true;
        this.disabledByDependency = false;

        if(this.options.triggerFunction !== null){
            var triggerFunc = this.options.triggerFunction;
            this.options.triggerFunction = function() {eval(triggerFunc)};
        }

        if(this.options.isInstance){
            this.instanceArray = null;
            this.clone = null; // Clone of the original HTML, only initiates if instances are turned on
        }
        else { // do parentInstance functions
            this.clone = this.component.clone();
            this.instanceArray = [this];
            this.createInstanceButton();
            this.iterations = 1;
        }

        // Intitialize the implemented component
        this.initialize();
        this.reformValidations();

        // Initiation functions
        this.addHighlightListeners();
        this.defineComponentChangedEventListener();
        this.catchComponentChangedEventListener();
        this.addDependencyListeners();

        // Add a tip if there is content to add
        if($.trim(this.tipDiv.html()) !== '') {
            this.addTip();
        }

        // Tip listeners
        this.addTipListeners();
    },

    addHighlightListeners: function() {
        var self = this;

        // Focus
        this.component.find(':input:not(button)').each(function(key, input) {
            $(input).bind('focus', function() {
                self.highlight();
            } );
            $(input).bind('blur', function(event) {
                self.removeHighlight();

                // Handle name highlight and validation
                if(self.type == 'JFormComponentName' && self.changed === true){
                    self.validate();
                }
            });
        });

        // Multiple choice
        if(this.component.find('input:checkbox, input:radio').length > 0) {
            this.component.mouseenter(function(event) {
                self.highlight();

            });
            this.component.mouseleave(function(event) {
                self.removeHighlight();
            });
        }

        return this;
    },

    reformValidations: function() {
    var reformedValidations = {},
    self = this;

    $.each(this.options.validationOptions, function(validationFunction, validationOptions) {
        // Check to see if the name of the function is actually an array index
        if(validationFunction >= 0) {
            validationFunction = validationOptions;
            validationOptions = {};
        }
        // Check to see if the validation options is not an object
        if(typeof(validationOptions) != 'object') {
            var tempValue = validationOptions;
            validationOptions = {};
            validationOptions[validationFunction] = tempValue;
        }
        validationOptions.value = '';
        reformedValidations[validationFunction] = validationOptions;

        // For serverSide
        if(reformedValidations.serverSide) {
            reformedValidations.serverSide.component = self.component;
        }
    });

    this.options.validationOptions = reformedValidations;
    },


    defineComponentChangedEventListener: function() {
        var self = this;

        // Handle IE events
        this.component.find('input:checkbox, input:radio').each(function(key, input) {
            $(input).bind('click', function(event) {
                $(this).trigger('jFormComponent:changed', self);
            });
        });

        this.component.find(':input:not(button, :checkbox, :radio)').each(function(key, input) {
            $(input).bind('change', function(event) {
                $(this).trigger('jFormComponent:changed', self);
            });
        });
    },

    catchComponentChangedEventListener: function() {
        var self = this;
        this.component.bind('jFormComponent:changed', function(event) {
            // Run a trigger on change if there is one
            if(self.options.triggerFunction !== null) {
                self.options.triggerFunction();
            }
            if(self.type == 'JFormComponentName'){
                self.changed = true;
            }
            // Validate the component on change
            self.validate();
        });
    },

    highlight: function() {
        // Add the highlight class and trigger the highlight
        this.component.addClass('jFormComponentHighlight').trigger('jFormComponent:highlighted', this.component);
        this.component.trigger('jFormComponent:showTip', this.component);
    },

    removeHighlight: function() {
        var self = this;
        this.component.removeClass('jFormComponentHighlight').trigger('jFormComponent:highlightRemoved', this.component);

        // Wait just a microsecond to see if you are still on the same component
        setTimeout(function() {
            if(!self.component.hasClass('jFormComponentHighlight')){
                self.component.trigger('jFormComponent:hideTip', self.component);
            }
        }, 1);
    },

    getData: function() {
        var self = this;
        if(this.instanceArray.length > 1) {
            this.formData = [];
            $.each(this.instanceArray, function(index, component) {
                var componentValue = component.getValue();
                if(componentValue != ''){
                    self.formData.push(componentValue);
                }
            });
        }
        else {
            this.formData = this.getValue();
        }
        //console.log(this.type);
        return this.formData;
    },

    setData: function(data) {
        var self = this;
        if($.isArray(data)) {
            $.each(data, function(index, value) {
                if((self.type == 'JFormComponentMultipleChoice' && ($.isArray(value) ||  self.multipeChoiceType == 'radio')) || self.type != 'JFormComponentMultipleChoice'){
                    if(index !== 0 && self.instanceArray[index] == undefined){
                        self.addInstance();
                    }
                    self.instanceArray[index].setValue(value);
                }
                else {
                    self.setValue(data);
                    return false;
                }
            });
        }
        else {
            this.setValue(data);
        }
    },

    addDependencyListeners: function() {
        var self = this;
        if(this.options.dependencyConditionObject) {
            this.toggleDependencyLock(true);
            $.each(this.options.dependencyConditionObject, function(key, condition) {
                if(key == 'and') {
                    $.each(condition, function(andKey, andCondition){
                        addDependencyListener($('#'+andKey));
                    });
                }
                else {
                    addDependencyListener($('#'+key));
                }
            });
        }

        function addDependencyListener(input) {
            input.bind('jFormComponent:changed', function() {
                self.checkDependencies();
            });

            // Force check dependencies on keyup for
            if(input.is('input:text, textarea')) {
                $(input).bind('keyup', function(event) {
                    self.checkDependencies();
                });
            }
        }
    },

    toggleDependencyLock: function(hide) {
        // Hide or lock the dependency
        var page = this.parentJFormSection.parentPage.jFormer.getActivePage();
        if(hide) {
            this.disabledByDependency = true;
            if(this.options.dependencyDisplay == 'hidden'){
                this.component.hide();
                if(page != null){
                    this.parentJFormSection.parentPage.jFormer.adjustHeight();
                }
            }
            else {
                this.component.addClass('jFormComponentDependencyDisabled').find('input, select, textarea').each(function(index, input){
                    input.disabled = true;
                });
            }
        }
        else {
            this.disabledByDependency = false;
            if(this.options.dependencyDisplay == 'hidden'){
                this.component.show();
                if(page != null){
                    this.parentJFormSection.parentPage.jFormer.adjustHeight();
                }
            }
            else {
                this.component.removeClass('jFormComponentDependencyDisabled').find('input, select, textarea').each(function(index, input){
                    input.disabled = false;
                });
            }
        }
        return this;
    },

    checkDependencies: function() {
        // Hide or show the section based on whether or not the dependency requirements are met
        var self = this
        var hide = true;
        $.each(this.options.dependencyConditionObject, function(key, condition) {
            if(key == 'and') {
                $.each(condition, function(andKey, andCondition) {
                    if((andCondition == 'validate' && self.parentJFormSection.components[andKey].validate()) || $('#'+andKey).val() == andCondition ){
                        hide = false;
                    }
                    else {
                        hide = true;
                        return false;
                    }
                });
                if(hide === false){
                    return false;
                }
            }
            else if((condition == 'validate' && self.parentJFormSection.components[key].validate()) || $('#'+key).val() == condition){
                hide = false;
                return false;
            }
            else {
                hide = true;
            }
        });
        //console.log(this.id, hide);
        this.toggleDependencyLock(hide);

        return this;
    },

    createInstanceButton:function() {
        var self =  this;
        if(this.options.instancesAllowed != 1){
          this.component.after('<button id="'+this.id+'-addInstance" class="jFormComponentAddInstanceButton">'+this.options.instanceAddText+'</button>');
          this.parentJFormSection.section.find('#'+this.id+'-addInstance').bind('click', function(event){
              event.preventDefault();
              self.addInstance();
          });
      }
    },

    addInstance: function() {
        var parent = this;
        if(this.instanceArray.length < this.options.instancesAllowed || this.options.instancesAllowed === 0) {
            var instanceClone = this.clone.clone();
            var addButton = this.parentJFormSection.section.find('#'+this.id+'-addInstance');

            // Create the remove button
            $(instanceClone).append('<button id="'+this.id+'-removeInstance" class="jFormComponentRemoveInstanceButton">'+this.options.instanceRemoveText+'</button>');

            // Add an event listener on the remove button
            instanceClone.find('#'+this.id+'-removeInstance').bind('click', function(event){
                var target = $(event.target);
                event.preventDefault();

                parent.instanceArray = $.map(parent.instanceArray, function(cloneId, index){
                   if(cloneId.component.attr('id') ==  target.parent().attr('id')){
                       if(cloneId.tip != null){
                            cloneId.tip.hide();
                       }
                       cloneId = null;
                   }
                   return cloneId;
                });
                target.parent().remove();
                target.remove();

                if(parent.instanceArray.length <= parent.options.instancesAllowed || parent.options.instancesAllowed === 0) {
                    addButton.show();
                }
                parent.relabelInstances(parent.instanceArray);
            });

            // Insert the clone right before the add button
            addButton.before(instanceClone);

            this.nameInstance(instanceClone);

            var instanceObject = this.createInstanceObject(instanceClone, this.options);
            this.instanceArray.push(instanceObject);
            this.relabelInstances(this.instanceArray);
            if(this.instanceArray.length == this.options.instancesAllowed && this.options.instancesAllowed !== 0) {
                addButton.hide();
            }

            // Resize the page
            //parent.parentJFormSection.parentJFormPage.scrollTo();
        }
        return this;
    },

    nameInstance: function(component) {
        component = $(component);
        var self = this,
        ending = '';
        this.iterations++;
        component.attr('id', component.attr('id').replace('-wrapper', '-instance'+this.iterations+'-wrapper'));
        component.find('*').each(function(key, child){
            if($(child).attr('id')){
                changeName(child, 'id');
            }
            if($(child).attr('for')){
                changeName(child, 'for');
            }
            if($(child).attr('name')){
                changeName(child, 'name');
            }
        });
        function changeName(child, attribute){
            ending = getEnding($(child).attr(attribute)) ;
            if(ending == ''){
                $(child).attr(attribute, $(child).attr(attribute) +'-instance'+self.iterations+ending);
            }else {
                $(child).attr(attribute, $(child).attr(attribute).replace(ending, '-instance'+self.iterations+ending));
            }
        }
        function getEnding(identifier){
            var ending = '';
            if(identifier.match(/\-(div|label|tip|removeInstance)\b/)){
                ending = identifier.match(/\-(div|label|tip|removeInstance)\b/)[0];
            } else {

            }
            return ending;
        }
        return component;
    },

    createInstanceObject:function(instanceClone, options){
        options.isInstance = true;
        options.triggerFunction = null;
        var instanceObject = new window[this.type](this.parentJFormSection, this.id+'-instance'+this.iterations, this.type, options);
        return instanceObject;
    },

    relabelInstances:function(instanceArray){
        $.each(instanceArray, function(key, instance){
            if( key!== 0) {
                var count = key+1,
                label = instance.component.find('#'+instance.component.attr('id').replace('-wrapper','-label'));
                if(label.length > 0) {
                    label.html(label.html().replace(/(\([0-9]+\) )?:/, ' ('+count+') :'));
                } else {
                    label = instance.component.find('label');
                    if (label.text().match(/(\([0-9]+\))$/)){
                        label.text(label.text().replace(/(\([0-9]+\))$/, '('+count+')'));
                    } else {
                        label.text(label.text() + ' ('+count+')');
                    }
                }

            }
        });
       this.parentJFormSection.parentPage.jFormer.adjustHeight();
    },

    addTip: function() {
        var self = this;

        // Check to see if the tip already exists
        if(typeof(this.tip) !== 'function') {
            // Create the tip
            var tip = this.tipTarget.simpletip({
                persistent: true,
                focus: true,
                position: 'topRight',
                content: self.tipDiv.html(),
                baseClass: 'jFormerTip',
                hideEffect: 'none',
                onShow: function(){
                    var offset = this.getPos().top + this.getTooltip().outerHeight() + 12;
                    var top = '';
                    if(utility.isSet(window.scrollY)) {
                        top = window.scrollY;
                    }
                    else { // IE FTL
                        top = document.documentElement.scrollTop;
                    }
                    //console.log(top, $(window).height(), offset)
                    if(top + $(window).height() > offset) {
                        //console.log('i can see it all');
                    } else {
                        //console.log('scroll to see it');
                        $.scrollTo(offset - $(window).height() + 'px', 250, {axis:'y'});
                    }
                }
            });
            this.tip = tip.simpletip();
        }
    },

    addTipListeners: function() {
        var self = this;

        // Show a tip
        this.component.bind('jFormComponent:showTip', function(event) {
            // Make sure the tip exists and display the tip if it is not empty
            if(self.tip && typeof(self.tip) == 'object' && $.trim(self.tipDiv.html()) !== '') {
                self.tip.show();
            }
        });

        // Hide a tip
        this.component.bind('jFormComponent:hideTip', function(event) {
            // Make sure the tip exists
            if(self.tip && typeof(self.tip) == 'object') {
                self.tip.hide();
            }
        });

        return this;
    },

    addEmptyValue: function(){
        var emptyValue = this.options.emptyValue,
        input = this.component.find('input');
        input.addClass('defaultValue');
        input.val(emptyValue);

        var target ='';
        input.focus(function(event){
            target = $(event.target);
            if ($.trim(target.val()) == emptyValue ){
                target.val('');
                target.removeClass('defaultValue');
            }
        });
        input.blur(function(event){
                target = $(event.target);
                if ($.trim(target.val()) == '' ){
                target.addClass('defaultValue');
                target.val(emptyValue);
            }
        });

    },

    clearValidation: function(){
        // Reset the error message array and validation passes boolean
        this.errorMessageArray = [];
        this.validationPassed = true;

        // Reset the classes
        this.component.removeClass('jFormComponentValidationFailed');
        this.component.addClass('jFormComponentValidationPassed');

        // Remove any tipErrorUl from the tip div
        this.component.find('.tipErrorUl').remove();

        // Handle tip display
        if(this.tip && typeof(this.tip) == 'object') {
            // Update the tip content
            this.tip.update(this.tipDiv.html());

            // Hide the tip if the tip is empty
            if($.trim(this.tipDiv.html()) == ''){
                this.tip.hide();
            }
        }
    },

    // Abstract functions
    initialize: function() { },
    getValue: function() { },
    setValue: function() { },

    validate: function() {
        // If there are no validations, return true
        if(this.options.validationOptions.length < 1) {
            return true;
        }

        var self = this;
        this.clearValidation();
        var value = this.getValue();

        if (value === null){
            return true;
        }

        $.each(this.options.validationOptions, function(validationType, validationOptions){
            // Handle required validations for non standard validations
            if(self.type != 'JFormComponentSingleLineText' && self.type != 'JFormComponentTextArea' && self.type != 'JFormComponentDate'){
                validationType = validationType+self.type;
            }
            validationOptions.value = value;
            var validation = self.parentJFormSection.parentPage.jFormer.jValidator.validate(validationType, validationOptions);

            if(validation == 'success') {
                return;
            }
            else {
                $.merge(self.errorMessageArray, validation);
            }
        });
        if(this.errorMessageArray.length > 0 ) {
            this.handleErrors();
            this.validationPassed = false;
        }
        return this.validationPassed;
    },

    handleServerValidationResponse: function(errorMessageArray) {
      if(errorMessageArray.length > 0) {
            $.each(this.instanceArray, function(key, instance){
                if(errorMessageArray[key].length > 0){
                    instance.errorMessageArray = errorMessageArray
                    instance.validationPassed = false;
                    instance.handleErrors();
                }
            });
        }
    },

    handleErrors: function() {
        var self = this;

        // Change classes
        this.component.removeClass('jFormComponentValidationPassed');
        this.component.addClass('jFormComponentValidationFailed');

        // Add a tip div and tip neccesary
        if(this.tipDiv.length == 0) {
            this.createTipDiv();
        }

        // Put the error list into the tip
        var tipErrorUl = $('<ul id="'+this.id+'-tipErrorUl" class="tipErrorUl"></ul>');
        $.each(this.errorMessageArray, function(index, errorMessage){
            tipErrorUl.append("<li>"+errorMessage+"</li>");
        });
        this.tipDiv.append(tipErrorUl);

        // Update the tip content
        this.tip.update(self.tipDiv.html());

        // Show the tip if you are currently on it
        if(this.component.hasClass('jFormComponentHighlight')) {
            this.tip.show();
        }
    },

    createTipDiv: function() {
        // Create a tip div and tip neccesary
        this.tipDiv = $('<div id="'+self.id+'-tip" style="display: none;"></div>');
        this.component.append(this.tipDiv);
        this.addTip();
    }
});JFormComponentDate = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    initialize: function(){
        this.tipTarget = this.component;
        this.addCalendar();
    },

    addCalendar: function(){
        this.component.find('input:text').date_input();
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        return $('#'+this.id).val();
    },

    setValue: function(value) {
        return $('#'+this.id).val(value);
    }

});JFormComponentDropDown = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    initialize: function(){
       this.tipTarget = this.component.find('select:last');
    },

    getValue: function() {
        if(this.disabledByDependency){
           return null;
        }
            var dropDownValue = this.component.find('option:selected').val();
            return dropDownValue;
    },

    setValue: function(value){
      this.component.find('option[value=\''+value+'\']').attr('selected', 'selected').trigger('component:changed');
    }

});JFormComponentFile = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        return this.component.val();
    },

    validate: function() {
        this._super();
    }
});JFormComponentHidden = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        return $('#'+this.id).val();
    },

    validate: function() {
        this._super();
    }
});
JFormComponentMultipleChoice = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    initialize: function(){
        this.tipTarget = this.component;
        this.addChoiceTips();
    },

    addChoiceTips: function(){
        var self = this;
        if(this.component.find('div.jFormComponentMultipleChoiceTip').length > 0) {
            this.component.find('div.jFormComponentMultipleChoiceTip').each(function(index, tip) {
                var tipTarget = self.component.find('label[for=\''+$(tip).attr('id').replace('-tip', '')+'\']').parent();
                tipTarget.simpletip({
                    position: 'topRight',
                    content: $(tip).html(),
                    baseClass: 'jFormerTip jFormComponentMultipleChoiceTip',
                    hideEffect: 'none'
                });
            });
        }
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        var multipleChoiceValue
        if(this.options.multipleChoiceType == 'checkbox') {
            multipleChoiceValue = [];
            this.component.find('input:checked').each(function(index, input){
                multipleChoiceValue.push($(input).val());
            });
        }
        else {
            if(this.component.find('input:checked').length > 0){
                multipleChoiceValue = this.component.find('input:checked').val();
            }
            else {
                multipleChoiceValue = '';
            }
        }
        return multipleChoiceValue;
    },

    setValue: function(data) {
        var self = this;
        if(this.options.multipleChoiceType == 'checkbox') {
            $.each(data, function(key, value){
                self.component.find('input[value=\''+value+'\']').attr('checked', 'checked').trigger('component:changed');
            });
        }
        else {
            this.component.find('input[value=\''+data+'\']').attr('checked', 'checked').trigger('component:changed');
        }
    }
});JFormComponentName = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    initialize: function(){
        this.tipTarget = this.component.find('input:last');
        this.addEmptyValues();
        this.changed = false;
    },

    setValue: function(data) {
        var self = this;
        $.each(data, function(key, value){
            if(data[key] != self.options.emptyValue[key]){
                self.component.find('input[id*='+key+']').focus().val(value).trigger('component:changed').blur();
            }
        });
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        var name = {},
        self = this;
        this.component.find('input').each(function(index, input) {
            name[$(input).attr('id').replace(self.id+'-', '')] = $(input).val();
        });
        if(name.firstName == this.options.emptyValue.firstName){
           name.firstName = '';
        }
        if(this.component.find('input[id$=middleInitial]').hasClass('defaultValue') ){
           name.middleInitial = '';
        }
        if(name.lastName == this.options.emptyValue.lastName){
           name.lastName = '';
        }

        return name;
    },

    validate: function(){
        var self = this;
        if(!this.changed){
            this._super();
        }

        setTimeout(function() {
            if(!self.component.hasClass('jFormComponentHighlight')){
                if(self.options.validationOptions.length < 1){
                    return true;
                }
                self.clearValidation();
                $.each(self.options.validationOptions, function(validationType, validationOptions){
                    validationType = validationType+self.type;
                    validationOptions['value'] = self.getValue();
                    var validation = self.parentJFormSection.parentPage.jFormer.jValidator.validate(validationType, validationOptions);
                    if(validation == 'success'){
                        return;
                    }
                    else {
                        $.merge(self.errorMessageArray, validation);
                        self.validationPassed = false;
                    }
                });
                if(self.errorMessageArray.length > 0 ){
                    self.handleErrors();
                }
                self.changed = false;
                return self.validationPassed;
            }
        }, 1);
    },

    addEmptyValues: function(){
        var self = this,
        emptyValue = this.options.emptyValue;
        $.each(emptyValue, function(key, value){
            var input = self.component.find('input[id*='+key+']');
            input.addClass('defaultValue');
            input.focus(function(event){
                if ($.trim($(event.target).val()) == value ){
                    $(event.target).val('');
                    $(event.target).removeClass('defaultValue');
                }
            });
            input.blur(function(event){
                if ($.trim($(event.target).val()) == '' ){
                    $(event.target).addClass('defaultValue');
                    $(event.target).val(value);
                }
            });
        });
    }
});
JFormComponentSingleLineText = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType ,options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType ,options);
    },

    initialize: function() {
        this.tipTarget = this.component.find('input:last');
        if(this.options.mask) {
            this.addMask();
        }
        if(this.options.emptyValue) {
            this.addEmptyValue();
        }
        if(this.component.find('input:password').length == 1 && this.options.showStrength){
            this.addPasswordStrength();
        }
    },

    addMask: function(){
        this.component.find('input').mask("?"+this.options.mask, {
                placeholder:' '
            });
    },

    addPasswordStrength: function(){
        var self = this,
        component = this.component;

        var strengthComponent = "<p id='"+this.id+"-strength' > Strength: <b> " + this.getPasswordStrength().strength + " </b> </p>";
        component.find('div.jFormComponentTip').append(strengthComponent);
        component.find('input:password').bind('keyup', function(event){
           component.find('#'+self.id+'-strength b').text(self.getPasswordStrength().strength);
           self.tip.update(component.find('div.jFormComponentTip').html());
        });
    },

    getPasswordStrength: function() {
        var value = this.getValue(),
        score = 0,
        strength = 'None';

        if(value.length >= 6) {
            score = (score + 1); // at least six characters
        }
        if(value.length >= 10) {
            score = (score + 1); // 10 characters+ bonus
        }
        if(value.match(/[a-z]/)) { // [verified] at least one lower case letter
            score = (score + 1);
        }
        if(value.match(/[A-Z]/)) { // [verified] at least one upper case letter
            score = (score + 1);
        }
        if(value.match(/\d+/)) { // [verified] at least one number
            score = (score + 1);
        }
        if(value.match(/(\d.*\d)/)) { // [verified] at least two numbers
            score = (score + 1);
        }
        if(value.match(/[!,@#$%\^&*?_~]/)) { // [verified] at least one special character
            score = (score + 1);
        }
        if(value.match(/([!,@#$%\^&*?_~].*[!,@#$%\^&*?_~])/)) { // [verified] at least two special characters
            score = (score + 1);
        }
        if(value.match(/[a-z]/) && value.match(/[A-Z]/)) { // [verified] both upper and lower case
            score = (score + 1);
        }
        if(value.match(/\d/) && value.match(/\D/)) { // [verified] both letters and numbers
            score = (score + 1);
        }
        if(value.match(/[a-z]/) && value.match(/[A-Z]/) && value.match(/\d/) && value.match(/[!,@#$%\^&*?_~]/)) {
            score = (score + 1);
        }

        if(score === 0) {
            strength = 'None';
        }
        else if(score <= 1) {
            strength = 'Very Weak';
        }
        else if(score <= 3) {
            strength = 'Weak';
        }
        else if(score <= 5) {
            strength = 'Good';
        }
        else if(score <= 7) {
            strength = 'Strong';
        }
        else if(score > 7) {
            strength = 'Very Strong';
        }

        return {
            'score': score,
            'strength': strength
        };
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        var input = $('#'+this.id).val();
        if(this.options.emptyValue){
            if( input == this.options.emptyValue){
                return '';
            }
            else {
                return input;
            }
        } else {
            return input;
        }
    },

    setValue: function(value) {
        $('#'+this.id).val(value).removeClass('defaultValue').trigger('component:changed');
    }

});

JFormComponentTextArea = JFormComponent.extend({
    init: function(parentJFormSection, jFormComponentId, jFormComponentType, options) {
        this._super(parentJFormSection, jFormComponentId, jFormComponentType, options);
    },

    initialize: function() {
        this.tipTarget = this.component.find('textarea');
        if(this.options.emptyValue) {
            this.addEmptyValue();
        }
    },

    getValue: function() {
        if(this.disabledByDependency || this.parentJFormSection.disabledByDependency){
           return null;
        }
        return $('#'+this.id).val();
    },

    setValue: function(value) {
        $('#'+this.id).val(value).trigger('component:changed');
    }

});/*
	Masked Input plugin for jQuery
	Copyright (c) 2007-2009 Josh Bush (digitalbush.com)
	Licensed under the MIT license (http://digitalbush.com/projects/masked-input-plugin/#license)
	Version: 1.2.2 (03/09/2009 22:39:06)
*/
(function(a){var c=(a.browser.msie?"paste":"input")+".mask";var b=(window.orientation!=undefined);a.mask={definitions:{"9":"[0-9]",a:"[A-Za-z]","*":"[A-Za-z0-9]"}};a.fn.extend({caret:function(e,f){if(this.length==0){return}if(typeof e=="number"){f=(typeof f=="number")?f:e;return this.each(function(){if(this.setSelectionRange){this.focus();this.setSelectionRange(e,f)}else{if(this.createTextRange){var g=this.createTextRange();g.collapse(true);g.moveEnd("character",f);g.moveStart("character",e);g.select()}}})}else{if(this[0].setSelectionRange){e=this[0].selectionStart;f=this[0].selectionEnd}else{if(document.selection&&document.selection.createRange){var d=document.selection.createRange();e=0-d.duplicate().moveStart("character",-100000);f=e+d.text.length}}return{begin:e,end:f}}},unmask:function(){return this.trigger("unmask")},mask:function(j,d){if(!j&&this.length>0){var f=a(this[0]);var g=f.data("tests");return a.map(f.data("buffer"),function(l,m){return g[m]?l:null}).join("")}d=a.extend({placeholder:"_",completed:null},d);var k=a.mask.definitions;var g=[];var e=j.length;var i=null;var h=j.length;a.each(j.split(""),function(m,l){if(l=="?"){h--;e=m}else{if(k[l]){g.push(new RegExp(k[l]));if(i==null){i=g.length-1}}else{g.push(null)}}});return this.each(function(){var r=a(this);var m=a.map(j.split(""),function(x,y){if(x!="?"){return k[x]?d.placeholder:x}});var n=false;var q=r.val();r.data("buffer",m).data("tests",g);function v(x){while(++x<=h&&!g[x]){}return x}function t(x){while(!g[x]&&--x>=0){}for(var y=x;y<h;y++){if(g[y]){m[y]=d.placeholder;var z=v(y);if(z<h&&g[y].test(m[z])){m[y]=m[z]}else{break}}}s();r.caret(Math.max(i,x))}function u(y){for(var A=y,z=d.placeholder;A<h;A++){if(g[A]){var B=v(A);var x=m[A];m[A]=z;if(B<h&&g[B].test(x)){z=x}else{break}}}}function l(y){var x=a(this).caret();var z=y.keyCode;n=(z<16||(z>16&&z<32)||(z>32&&z<41));if((x.begin-x.end)!=0&&(!n||z==8||z==46)){w(x.begin,x.end)}if(z==8||z==46||(b&&z==127)){t(x.begin+(z==46?0:-1));return false}else{if(z==27){r.val(q);r.caret(0,p());return false}}}function o(B){if(n){n=false;return(B.keyCode==8)?false:null}B=B||window.event;var C=B.charCode||B.keyCode||B.which;var z=a(this).caret();if(B.ctrlKey||B.altKey||B.metaKey){return true}else{if((C>=32&&C<=125)||C>186){var x=v(z.begin-1);if(x<h){var A=String.fromCharCode(C);if(g[x].test(A)){u(x);m[x]=A;s();var y=v(x);a(this).caret(y);if(d.completed&&y==h){d.completed.call(r)}}}}}return false}function w(x,y){for(var z=x;z<y&&z<h;z++){if(g[z]){m[z]=d.placeholder}}}function s(){return r.val(m.join("")).val()}function p(y){var z=r.val();var C=-1;for(var B=0,x=0;B<h;B++){if(g[B]){m[B]=d.placeholder;while(x++<z.length){var A=z.charAt(x-1);if(g[B].test(A)){m[B]=A;C=B;break}}if(x>z.length){break}}else{if(m[B]==z[x]&&B!=e){x++;C=B}}}if(!y&&C+1<e){r.val("");w(0,h)}else{if(y||C+1>=e){s();if(!y){r.val(r.val().substring(0,C+1))}}}return(e?B:i)}if(!r.attr("readonly")){r.one("unmask",function(){r.unbind(".mask").removeData("buffer").removeData("tests")}).bind("focus.mask",function(){q=r.val();var x=p();s();setTimeout(function(){if(x==j.length){r.caret(0,x)}else{r.caret(x)}},0)}).bind("blur.mask",function(){p();if(r.val()!=q){r.change()}}).bind("keydown.mask",l).bind("keypress.mask",o).bind(c,function(){setTimeout(function(){r.caret(p(true))},0)})}p()})}})})(jQuery);/**
 * jQuery.ScrollTo - Easy element scrolling using jQuery.
 * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 3/9/2009
 * @author Ariel Flesler
 * @version 1.4.1
 *
 * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
 */
;(function($){var m=$.scrollTo=function(b,h,f){$(window).scrollTo(b,h,f)};m.defaults={axis:'xy',duration:parseFloat($.fn.jquery)>=1.3?0:1};m.window=function(b){return $(window).scrollable()};$.fn.scrollable=function(){return this.map(function(){var b=this,h=!b.nodeName||$.inArray(b.nodeName.toLowerCase(),['iframe','#document','html','body'])!=-1;if(!h)return b;var f=(b.contentWindow||b).document||b.ownerDocument||b;return $.browser.safari||f.compatMode=='BackCompat'?f.body:f.documentElement})};$.fn.scrollTo=function(l,j,a){if(typeof j=='object'){a=j;j=0}if(typeof a=='function')a={onAfter:a};if(l=='max')l=9e9;a=$.extend({},m.defaults,a);j=j||a.speed||a.duration;a.queue=a.queue&&a.axis.length>1;if(a.queue)j/=2;a.offset=n(a.offset);a.over=n(a.over);return this.scrollable().each(function(){var k=this,o=$(k),d=l,p,g={},q=o.is('html,body');switch(typeof d){case'number':case'string':if(/^([+-]=)?\d+(\.\d+)?(px)?$/.test(d)){d=n(d);break}d=$(d,this);case'object':if(d.is||d.style)p=(d=$(d)).offset()}$.each(a.axis.split(''),function(b,h){var f=h=='x'?'Left':'Top',i=f.toLowerCase(),c='scroll'+f,r=k[c],s=h=='x'?'Width':'Height';if(p){g[c]=p[i]+(q?0:r-o.offset()[i]);if(a.margin){g[c]-=parseInt(d.css('margin'+f))||0;g[c]-=parseInt(d.css('border'+f+'Width'))||0}g[c]+=a.offset[i]||0;if(a.over[i])g[c]+=d[s.toLowerCase()]()*a.over[i]}else g[c]=d[i];if(/^\d+$/.test(g[c]))g[c]=g[c]<=0?0:Math.min(g[c],u(s));if(!b&&a.queue){if(r!=g[c])t(a.onAfterFirst);delete g[c]}});t(a.onAfter);function t(b){o.animate(g,j,a.easing,b&&function(){b.call(this,l,a)})};function u(b){var h='scroll'+b;if(!q)return k[h];var f='client'+b,i=k.ownerDocument.documentElement,c=k.ownerDocument.body;return Math.max(i[h],c[h])-Math.min(i[f],c[f])}}).end()};function n(b){return typeof b=='object'?b:{top:b,left:b}}})(jQuery);

/**
 * jQuery.LocalScroll - Animated scrolling navigation, using anchors.
 * Copyright (c) 2007-2009 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 3/11/2009
 * @author Ariel Flesler
 * @version 1.2.7
 **/
;(function($){var l=location.href.replace(/#.*/,'');var g=$.localScroll=function(a){$('body').localScroll(a)};g.defaults={duration:1e3,axis:'y',event:'click',stop:true,target:window,reset:true};g.hash=function(a){if(location.hash){a=$.extend({},g.defaults,a);a.hash=false;if(a.reset){var e=a.duration;delete a.duration;$(a.target).scrollTo(0,a);a.duration=e}i(0,location,a)}};$.fn.localScroll=function(b){b=$.extend({},g.defaults,b);return b.lazy?this.bind(b.event,function(a){var e=$([a.target,a.target.parentNode]).filter(d)[0];if(e)i(a,e,b)}):this.find('a,area').filter(d).bind(b.event,function(a){i(a,this,b)}).end().end();function d(){return!!this.href&&!!this.hash&&this.href.replace(this.hash,'')==l&&(!b.filter||$(this).is(b.filter))}};function i(a,e,b){var d=e.hash.slice(1),f=document.getElementById(d)||document.getElementsByName(d)[0];if(!f)return;if(a)a.preventDefault();var h=$(b.target);if(b.lock&&h.is(':animated')||b.onBefore&&b.onBefore.call(b,a,f,h)===false)return;if(b.stop)h.stop(true);if(b.hash){var j=f.id==d?'id':'name',k=$('<a> </a>').attr(j,d).css({position:'absolute',top:$(window).scrollTop(),left:$(window).scrollLeft()});f[j]='';$('body').prepend(k);location=e.hash;k.remove();f[j]=d}h.scrollTo(f,b).trigger('notify.serialScroll',[f])}})(jQuery);

/**
 * jQuery[a] - Animated scrolling of series
 * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 3/20/2008
 * @author Ariel Flesler
 * @version 1.2.1
 *
 * http://flesler.blogspot.com/2008/02/jqueryserialscroll.html
 */
;(function($){var a='serialScroll',b='.'+a,c='bind',C=$[a]=function(b){$.scrollTo.window()[a](b)};C.defaults={duration:1e3,axis:'x',event:'click',start:0,step:1,lock:1,cycle:1,constant:1};$.fn[a]=function(y){y=$.extend({},C.defaults,y);var z=y.event,A=y.step,B=y.lazy;return this.each(function(){var j=y.target?this:document,k=$(y.target||this,j),l=k[0],m=y.items,o=y.start,p=y.interval,q=y.navigation,r;if(!B)m=w();if(y.force)t({},o);$(y.prev||[],j)[c](z,-A,s);$(y.next||[],j)[c](z,A,s);if(!l.ssbound)k[c]('prev'+b,-A,s)[c]('next'+b,A,s)[c]('goto'+b,t);if(p)k[c]('start'+b,function(e){if(!p){v();p=1;u()}})[c]('stop'+b,function(){v();p=0});k[c]('notify'+b,function(e,a){var i=x(a);if(i>-1)o=i});l.ssbound=1;if(y.jump)(B?k:w())[c](z,function(e){t(e,x(e.target))});if(q)q=$(q,j)[c](z,function(e){e.data=Math.round(w().length/q.length)*q.index(this);t(e,this)});function s(e){e.data+=o;t(e,this)};function t(e,a){if(!isNaN(a)){e.data=a;a=l}var c=e.data,n,d=e.type,f=y.exclude?w().slice(0,-y.exclude):w(),g=f.length,h=f[c],i=y.duration;if(d)e.preventDefault();if(p){v();r=setTimeout(u,y.interval)}if(!h){n=c<0?0:n=g-1;if(o!=n)c=n;else if(!y.cycle)return;else c=g-n-1;h=f[c]}if(!h||d&&o==c||y.lock&&k.is(':animated')||d&&y.onBefore&&y.onBefore.call(a,e,h,k,w(),c)===!1)return;if(y.stop)k.queue('fx',[]).stop();if(y.constant)i=Math.abs(i/A*(o-c));k.scrollTo(h,i,y).trigger('notify'+b,[c])};function u(){k.trigger('next'+b)};function v(){clearTimeout(r)};function w(){return $(m,l)};function x(a){if(!isNaN(a))return a;var b=w(),i;while((i=b.index(a))==-1&&a!=l)a=a.parentNode;return i}})}})(jQuery);/**
 * jquery.simpletip 1.3.1. A simple tooltip plugin
 *
 * Copyright (c) 2009 Craig Thompson
 * http://craigsworks.com
 *
 * Licensed under GPLv3
 * http://www.opensource.org/licenses/gpl-3.0.html
 *
 * Launch  : February 2009
 * Version : 1.3.1
 * Released: February 5, 2009 - 11:04am
 */
(function($){

    function Simpletip(elem, conf)
    {
        var self = this;
        elem = jQuery(elem);

        var wrappedContent = ['<span class="tipArrow"></span><div class="tipContent">',conf.content,'</div>'].join('');

        var tooltip = jQuery(document.createElement('div'))
        .addClass(conf.baseClass)
        .addClass( (conf.fixed) ? conf.fixedClass : '' )
        .addClass( (conf.persistent) ? conf.persistentClass : '' )
        .html(wrappedContent)
        .insertAfter(elem);

        // Add an event listener that listens for a window resize and repositions the element
        jQuery(window).resize(function(){
            if(tooltip.is(':visible')) {
                self.updatePos();
            }

        });

        if(!conf.hidden) tooltip.show();
        else tooltip.hide();

        if(!conf.persistent)
        {
            elem.hover(
                function(event){
                    self.show(event)
                },
                function(){
                    self.hide()
                }
                );

            if(!conf.fixed)
            {
                elem.mousemove( function(event){
                    if(tooltip.css('display') !== 'none') self.updatePos(event);
                });
            };
        }
        else
        {
            elem.click(function(event)
            {
                if(event.target === elem.get(0))
                {
            //if(tooltip.css('display') !== 'none')
            // self.hide();
            // else
            //self.show();
            }
            });

            jQuery(window).mousedown(function(event)
            {
                if(tooltip.css('display') !== 'none')
                {
                    var check = (conf.focus) ? jQuery(event.target).parents('.tooltip').andSelf().filter(function(){
                        return this === tooltip.get(0)
                    }).length : 0;
                //if(check === 0) self.hide();
                };
            });
        };


        jQuery.extend(self,
        {
            getVersion: function()
            {
                return [1, 2, 0];
            },

            getParent: function()
            {
                return elem;
            },

            getTooltip: function()
            {
                return tooltip;
            },

            getPos: function()
            {
                return tooltip.offset();
            },

            setPos: function(posX, posY)
            {
                var elemPos = elem.offset();

                if(typeof posX == 'string') posX = parseInt(posX) + elemPos.left;
                if(typeof posY == 'string') posY = parseInt(posY) + elemPos.top;

                tooltip.css({
                    left: posX,
                    top: posY
                });

                return self;
            },

            show: function(event)
            {
                var onbefore = conf.onBeforeShow();
                if(onbefore === false){
                    return false;
                }
                self.updatePos( (conf.fixed) ? null : event );

                switch(conf.showEffect)
                {
                    case 'fade':
                        tooltip.fadeIn(conf.showTime); break;
                    case 'slide':
                        tooltip.slideDown(conf.showTime, self.updatePos); break;
                    case 'custom':
                        conf.showCustom.call(tooltip, conf.showTime); break;
                    default:
                    case 'none':
                        tooltip.show(); break;
                };

                tooltip.addClass(conf.activeClass);

                conf.onShow.call(self);

                return self;
            },

            hide: function()
            {
                conf.onBeforeHide.call(self);

                switch(conf.hideEffect)
                {
                    case 'fade':
                        tooltip.fadeOut(conf.hideTime); break;
                    case 'slide':
                        tooltip.slideUp(conf.hideTime); break;
                    case 'custom':
                        conf.hideCustom.call(tooltip, conf.hideTime); break;
                    default:
                    case 'none':
                        tooltip.hide(); break;
                };

                tooltip.removeClass(conf.activeClass);

                conf.onHide.call(self);

                return self;
            },

            update: function(content)
            {
                tooltip.find('.tipContent').html(content);
                conf.content = content;

                return self;
            },

            load: function(uri, data)
            {
                conf.beforeContentLoad.call(self);

                tooltip.load(uri, data, function(){
                    conf.onContentLoad.call(self);
                });

                return self;
            },

            boundryCheck: function(posX, posY)
            {
                var newX = posX + tooltip.outerWidth();
                var newY = posY + tooltip.outerHeight();

                var windowWidth = jQuery(window).width() + jQuery(window).scrollLeft();
                var windowHeight = jQuery(window).height() + jQuery(window).scrollTop();

                return [(newX >= windowWidth), (newY >= windowHeight)];
            },

            updatePos: function(event)
            {
                var tooltipWidth = tooltip.outerWidth();
                var tooltipHeight = tooltip.outerHeight();

                if(!event && conf.fixed)
                {
                    if(conf.position.constructor == Array)
                    {
                        posX = parseInt(conf.position[0]);
                        posY = parseInt(conf.position[1]);
                    }
                    else if(jQuery(conf.position).attr('nodeType') === 1)
                    {
                        var offset = jQuery(conf.position).offset();
                        posX = offset.left;
                        posY = offset.top;
                    }
                    else
                    {
                        var elemPos = elem.offset();
                        var elemWidth = elem.outerWidth();
                        var elemHeight = elem.outerHeight();

                        switch(conf.position)
                        {
                            case 'top':
                                var posX = elemPos.left - (tooltipWidth / 2) + (elemWidth / 2);
                                var posY = elemPos.top - tooltipHeight;
                                break;

                            case 'bottom':
                                var posX = elemPos.left - (tooltipWidth / 2) + (elemWidth / 2);
                                var posY = elemPos.top + elemHeight;
                                break;

                            case 'left':
                                var posX = elemPos.left - tooltipWidth;
                                var posY = elemPos.top - (tooltipHeight / 2) + (elemHeight / 2);
                                break;

                            case 'right':
                                var posX = elemPos.left + elemWidth;
                                var posY = elemPos.top - (tooltipHeight / 2) + (elemHeight / 2);
                                break;

                            case 'topRight':
                                var posX = elemPos.left + elemWidth;
                                var posY = elemPos.top;
                                break;

                            default:
                            case 'default':
                                var posX = (elemWidth / 2) + elemPos.left + 20;
                                var posY = elemPos.top;
                                break;
                        };
                    };
                }
                else
                {
                    var posX = event.pageX;
                    var posY = event.pageY;
                };

                if(typeof conf.position != 'object')
                {
                    posX = posX + conf.offset[0];
                    posY = posY + conf.offset[1];

                    if(conf.boundryCheck)
                    {
                        var overflow = self.boundryCheck(posX, posY);

                        if(overflow[0]) posX = posX - (tooltipWidth / 2) - (2 * conf.offset[0]);
                        if(overflow[1]) posY = posY - (tooltipHeight / 2) - (2 * conf.offset[1]);
                    }
                }
                else
                {
                    if(typeof conf.position[0] == "string") posX = String(posX);
                    if(typeof conf.position[1] == "string") posY = String(posY);
                };

                self.setPos(posX, posY);

                return self;
            }
        });
    };

    jQuery.fn.simpletip = function(conf)
    {
        // Check if a simpletip is already present
        var api = jQuery(this).eq(typeof conf == 'number' ? conf : 0).data("simpletip");
        if(api) return api;

        // Default configuration
        var defaultConf = {
            // Basics
            content: 'A simple tooltip',
            persistent: false,
            focus: false,
            hidden: true,

            // Positioning
            position: 'default',
            offset: [0, 0],
            boundryCheck: false,
            fixed: true,

            // Effects
            showEffect: 'fade',
            showTime: 150,
            showCustom: null,
            hideEffect: 'fade',
            hideTime: 150,
            hideCustom: null,

            // Selectors and classes
            baseClass: 'tooltip',
            activeClass: 'active',
            fixedClass: 'fixed',
            persistentClass: 'persistent',
            focusClass: 'focus',

            // Callbacks
            onBeforeShow: function(){
                return true;
            },
            onShow: function(){},
            onBeforeHide: function(){},
            onHide: function(){},
            beforeContentLoad: function(){},
            onContentLoad: function(){}
        };
        jQuery.extend(defaultConf, conf);

        this.each(function()
        {
            var el = new Simpletip(jQuery(this), defaultConf);
            jQuery(this).data("simpletip", el);
        });

        return this;
    };
})();