<commit_before>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                event.preventDefault();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                event.preventDefault();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            event.preventDefault();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn     = this.find(".primary"),
            buttonId        = null,
            which           = String.fromCharCode(e.which),
            $focusedElement = this.find(".dialog-button:focus, a:focus");

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && !inTextArea) {  // enter key in single-line text input still dismisses
            // Click focused button or primary if no focus
            if($focusedElement.length !== 0) {
                $focusedElement.click();
            } else {
                $primaryBtn.click();
            }
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            // Space bar on focused button or link
            if($focusedElement.length !== 0) {
                $focusedElement.click();
            }
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (autoDismiss && buttonId) {
            _dismissDialog(this, buttonId);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;

        this._$dlg.one("hidden", function () {
            $(".modal-wrapper:last").remove();
        });
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready with `cancelModalDialogIfOpen()`.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result  = $.Deferred(),
            promise = result.promise(),
            $dlg    = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();
            $(".modal-backdrop:last").addClass("last-backdrop");

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        if (autoDismiss) {
            $dlg.one("click", ".dialog-button", function (e) {
                _dismissDialog($dlg, $(this).attr("data-button-id"));
            });
        }

        $(".last-backdrop").removeClass("last-backdrop");

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex)
            .addClass("last-backdrop");

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<commit_msg>Simplify change<commit_after>/*
 * Copyright (c) 2012 Adobe Systems Incorporated. All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 *
 */

/*jslint vars: true, plusplus: true, devel: true, nomen: true, indent: 4, maxerr: 50 */
/*global define, $, brackets, window, Mustache */

/**
 * Utilities for creating and managing standard modal dialogs.
 */
define(function (require, exports, module) {
    "use strict";

    require("utils/Global");

    var KeyBindingManager = require("command/KeyBindingManager"),
        KeyEvent          = require("utils/KeyEvent"),
        NativeApp         = require("utils/NativeApp"),
        Strings           = require("strings"),
        DialogTemplate    = require("text!htmlContent/dialog-template.html");

    /**
     * Dialog Buttons IDs
     * @const {string}
     */
    var DIALOG_BTN_CANCEL           = "cancel",
        DIALOG_BTN_OK               = "ok",
        DIALOG_BTN_DONTSAVE         = "dontsave",
        DIALOG_CANCELED             = "_canceled",
        DIALOG_BTN_DOWNLOAD         = "download";

    /**
     * Dialog Buttons Class Names
     * @const {string}
     */
    var DIALOG_BTN_CLASS_PRIMARY    = "primary",
        DIALOG_BTN_CLASS_NORMAL     = "",
        DIALOG_BTN_CLASS_LEFT       = "left";

    /** @type {number} The z-index used for the dialogs. Each new dialog increase this number by 2 */
    var zIndex = 1050;

    /**
     * @private
     * Dismises a modal dialog
     * @param {$.Element} $dlg
     * @param {string} buttonId
     */
    function _dismissDialog($dlg, buttonId) {
        $dlg.data("buttonId", buttonId);
        $dlg.modal("hide");
    }

    /**
     * @private
     * Returns true if the modal dialog has a button with the given ID
     * @param {$.Element} $dlg
     * @param {string} buttonId
     * @return {boolean}
     */
    function _hasButton($dlg, buttonId) {
        return ($dlg.find("[data-button-id='" + buttonId + "']").length > 0);
    }

    /**
     * @private
     * Handles the use of Tab so that it stays inside the Dialog
     * @param {$.Event} event
     * @param {$.Element} $dlg
     */
    function _handleTab(event, $dlg) {
        var $inputs = $(":input:enabled, a", $dlg).filter(":visible");

        if ($(event.target).closest($dlg).length) {
            // If it's the first or last tabbable element, focus the last/first element
            if ((!event.shiftKey && event.target === $inputs[$inputs.length - 1]) ||
                    (event.shiftKey && event.target === $inputs[0])) {
                $inputs.filter(event.shiftKey ? ":last" : ":first").focus();
                event.preventDefault();

            // If there is no element to focus, don't let it focus outside of the dialog
            } else if (!$inputs.length) {
                event.preventDefault();
            }

        // If the focus left the dialog, focus the first element in the dialog
        } else {
            $inputs.first().focus();
            event.preventDefault();
        }
    }

    /**
     * Handles the keyDown event for the dialogs
     * @param {$.Event} e
     * @param {boolean} autoDismiss
     * @return {boolean}
     */
    var _keydownHook = function (e, autoDismiss) {
        var $primaryBtn     = this.find(".primary"),
            buttonId        = null,
            which           = String.fromCharCode(e.which),
            $focusedElement = this.find(".dialog-button:focus, a:focus");

        // There might be a textfield in the dialog's UI; don't want to mistake normal typing for dialog dismissal
        var inTextArea    = (e.target.tagName === "TEXTAREA"),
            inTypingField = inTextArea || ($(e.target).filter(":text, :password").length > 0);

        if (e.which === KeyEvent.DOM_VK_TAB) {
            _handleTab(e, this);
        } else if (e.which === KeyEvent.DOM_VK_ESCAPE) {
            buttonId = DIALOG_BTN_CANCEL;
        } else if (e.which === KeyEvent.DOM_VK_RETURN && !inTextArea) {  // enter key in single-line text input still dismisses
            // Click focused button or primary if no focus
            if($focusedElement.length !== 0) {
                $focusedElement.click();
            } else {
                $primaryBtn.click();
            }
        } else if (e.which === KeyEvent.DOM_VK_SPACE) {
            // Space bar on focused button or link
            if($focusedElement.length !== 0) {
                $focusedElement.click();
            }
        } else if (brackets.platform === "mac") {
            // CMD+D Don't Save
            if (e.metaKey && (which === "D")) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            // FIXME (issue #418) CMD+. Cancel swallowed by native shell
            } else if (e.metaKey && (e.which === KeyEvent.DOM_VK_PERIOD)) {
                buttonId = DIALOG_BTN_CANCEL;
            }
        } else { // if (brackets.platform === "win") {
            // 'N' Don't Save
            if (which === "N" && !inTypingField) {
                if (_hasButton(this, DIALOG_BTN_DONTSAVE)) {
                    buttonId = DIALOG_BTN_DONTSAVE;
                }
            }
        }

        if (autoDismiss && buttonId) {
            _dismissDialog(this, buttonId);
        }

        // Stop any other global hooks from processing the event (but
        // allow it to continue bubbling if we haven't otherwise stopped it).
        return true;
    };

    /**
     * @constructor
     * @private
     *
     * @param {$.Element} $dlg The dialog jQuery element
     * @param {$.Promise} promise A promise that will be resolved with the ID of the clicked button when the dialog
     *     is dismissed. Never rejected.
     */
    function Dialog($dlg, promise) {
        this._$dlg    = $dlg;
        this._promise = promise;
    }

    /** @type {$.Element} The dialog jQuery element */
    Dialog.prototype.getElement = function () {
        return this._$dlg;
    };

    /** @type {$.Promise} The dialog promise */
    Dialog.prototype.getPromise = function () {
        return this._promise;
    };

    /**
     * Closes the dialog if is visible
     */
    Dialog.prototype.close = function () {
        if (this._$dlg.is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
            _dismissDialog(this._$dlg, DIALOG_CANCELED);
        }
    };

    /**
     * Adds a done callback to the dialog promise
     */
    Dialog.prototype.done = function (callback) {
        this._promise.done(callback);
    };

    /**
     * Creates a new modal dialog from a given template.
     * The template can either be a string or a jQuery object representing a DOM node that is *not* in the current DOM.
     *
     * @param {string} template A string template or jQuery object to use as the dialog HTML.
     * @param {boolean=} autoDismiss Whether to automatically dismiss the dialog when one of the buttons
     *      is clicked. Default true. If false, you'll need to manually handle button clicks and the Esc
     *      key, and dismiss the dialog yourself when ready with `cancelModalDialogIfOpen()`.
     * @return {Dialog}
     */
    function showModalDialogUsingTemplate(template, autoDismiss) {
        if (autoDismiss === undefined) {
            autoDismiss = true;
        }

        $("body").append("

");

        var result  = $.Deferred(),
            promise = result.promise(),
            $dlg    = $(template)
                .addClass("instance")
                .appendTo(".modal-inner-wrapper:last");

        // Save the dialog promise for unit tests
        $dlg.data("promise", promise);

        var keydownHook = function (e) {
            return _keydownHook.call($dlg, e, autoDismiss);
        };

        // Pipe dialog-closing notification back to client code
        $dlg.one("hidden", function () {
            var buttonId = $dlg.data("buttonId");
            if (!buttonId) {    // buttonId will be undefined if closed via Bootstrap's "x" button
                buttonId = DIALOG_BTN_CANCEL;
            }

            // Let call stack return before notifying that dialog has closed; this avoids issue #191
            // if the handler we're triggering might show another dialog (as long as there's no
            // fade-out animation)
            window.setTimeout(function () {
                result.resolve(buttonId);
            }, 0);

            // Remove the dialog instance from the DOM.
            $dlg.remove();
            $(".modal-backdrop:last").addClass("last-backdrop");

            // Remove our global keydown handler.
            KeyBindingManager.removeGlobalKeydownHook(keydownHook);

            //Remove wrapper
            $(".modal-wrapper:last").remove();
        }).one("shown", function () {
            // Set focus to the default button
            var primaryBtn = $dlg.find(".primary");

            if (primaryBtn) {
                primaryBtn.focus();
            }

            // Push our global keydown handler onto the global stack of handlers.
            KeyBindingManager.addGlobalKeydownHook(keydownHook);
        });

        // Click handler for buttons
        if (autoDismiss) {
            $dlg.one("click", ".dialog-button", function (e) {
                _dismissDialog($dlg, $(this).attr("data-button-id"));
            });
        }

        $(".last-backdrop").removeClass("last-backdrop");

        // Run the dialog
        $dlg
            .modal({
                backdrop: "static",
                show:     true,
                selector: ".modal-inner-wrapper:last",
                keyboard: false // handle the ESC key ourselves so we can deal with nested dialogs
            })
            // Updates the z-index of the modal dialog and the backdrop
            .css("z-index", zIndex + 1)
            .next()
            .css("z-index", zIndex)
            .addClass("last-backdrop");

        zIndex += 2;

        return (new Dialog($dlg, promise));
    }

    /**
     * Creates a new general purpose modal dialog using the default template and the template variables given
     * as parameters as described.
     *
     * @param {string} dlgClass A class name identifier for the dialog. Typically one of DefaultDialogs.*
     * @param {string=} title The title of the dialog. Can contain HTML markup. Defaults to "".
     * @param {string=} message The message to display in the dialog. Can contain HTML markup. Defaults to "".
     * @param {Array.<{className: string, id: string, text: string}>=} buttons An array of buttons where each button
     *      has a class, id and text property. The id is used in "data-button-id". Defaults to a single Ok button.
     *      Typically className is one of DIALOG_BTN_CLASS_*, id is one of DIALOG_BTN_*
     * @return {Dialog}
     */
    function showModalDialog(dlgClass, title, message, buttons) {
        var templateVars = {
            dlgClass: dlgClass,
            title:    title   || "",
            message:  message || "",
            buttons:  buttons || [{ className: DIALOG_BTN_CLASS_PRIMARY, id: DIALOG_BTN_OK, text: Strings.OK }]
        };
        var template = Mustache.render(DialogTemplate, templateVars);

        return showModalDialogUsingTemplate(template);
    }

    /**
     * Immediately closes any dialog instances with the given class. The dialog callback for each instance will
     * be called with the special buttonId DIALOG_CANCELED (note: callback is run asynchronously).
     * @param {string} dlgClass The class name identifier for the dialog.
     * @param {string=} buttonId The button id to use when closing the dialog. Defaults to DIALOG_CANCELED
     */
    function cancelModalDialogIfOpen(dlgClass, buttonId) {
        $("." + dlgClass + ".instance").each(function () {
            if ($(this).is(":visible")) {   // Bootstrap breaks if try to hide dialog that's already hidden
                _dismissDialog($(this), buttonId || DIALOG_CANCELED);
            }
        });
    }

    exports.DIALOG_BTN_CANCEL            = DIALOG_BTN_CANCEL;
    exports.DIALOG_BTN_OK                = DIALOG_BTN_OK;
    exports.DIALOG_BTN_DONTSAVE          = DIALOG_BTN_DONTSAVE;
    exports.DIALOG_CANCELED              = DIALOG_CANCELED;
    exports.DIALOG_BTN_DOWNLOAD          = DIALOG_BTN_DOWNLOAD;

    exports.DIALOG_BTN_CLASS_PRIMARY     = DIALOG_BTN_CLASS_PRIMARY;
    exports.DIALOG_BTN_CLASS_NORMAL      = DIALOG_BTN_CLASS_NORMAL;
    exports.DIALOG_BTN_CLASS_LEFT        = DIALOG_BTN_CLASS_LEFT;

    exports.showModalDialog              = showModalDialog;
    exports.showModalDialogUsingTemplate = showModalDialogUsingTemplate;
    exports.cancelModalDialogIfOpen      = cancelModalDialogIfOpen;
});
<|endoftext|>


<commit_before>import {DOM_CHANGED_EVENT} from '../classes/fef-dom-observer';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const ANIMATION_SPEED = 200;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27
};

let existingModals = {};
let scrollbarWidth = 0;

$(window).on(DOM_CHANGED_EVENT, (e) => {
    scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
    $('[data-modal-id]').each((index, element) => {

        $(element).on('click', () => {
            let $caller = $(element);
            let modalId = $caller.attr('data-modal-id');
            let $modalElement = $(`[data-id=${modalId}]`);

            if (existingModals[modalId]) {
                existingModals[modalId].show();
            } else if ($modalElement.length > 0) {
                existingModals[modalId] = new FefModal($modalElement, $caller);
            }
        });
    });
});

/**
 * Handles showing and hiding a modal element
 */
export class FefModal {

    /**
     * @param $element jQuery element
     * @param $caller jQuery element
     */
    constructor($element, $caller) {
        this.$element = $element;
        this.$caller = $caller;
        this.$focusTarget = this.$element.find('.js-focus-target').first();
        this.$mainWrapper = this.$element.find('.js-modal-main-wrapper');
        this.$mainContent = this.$element.find('.js-modal-main-content');
        this.animation = this.$element.attr('data-animation');
        this.previousScrollPosition = null;

        this.endOfModalClass = ".js-end-of-modal";
        $element.append('');

        this.bindEvents();

        if (this.$element.hasClass('js-min-height-of-masthead')) {
            this.$mainContent.css('min-height', $('.js-masthead').outerHeight());
        }

        this.show();
    }

    /**
     * Binds the relevant events for this modal:
     * - Click on a close-button or the overlay
     * - Pressing Escape
     */
    bindEvents() {
        this.$element.find('.js-close-modal, .js-modal-overlay').on('click', () => {
            this.close();
        });

        this.$element.on('keydown', (e) => {
            if (e.keyCode === KEYCODES.escape) {
                this.close();
            }
        });

        // A11Y Helper: when tabbing out of the modal --> on focus, close modal, set focus to the caller
        $(this.endOfModalClass).on('focus', () => {
            this.close();
            $(this.endOfModalClass).remove();
        });

    }

    /**
     * Show the modal, depending on the provided animation.
     */
    show() {
        const onShowFinished = () => {
            this.preventScrolling();

            if (this.$focusTarget.length === 1 && (FefResponsiveHelper.isDesktop() || FefResponsiveHelper.isDesktopWide()) ) {
                this.setFocus(this.$focusTarget);
            }
        };

        this.$caller.attr('aria-expanded', true);
        this.$caller.attr('aria-haspopup', true);

        switch (this.animation) {
            case 'scale-from-origin':
                this.scaleFromOrigin(onShowFinished);
                break;
            case 'fade-in-out':
                this.$element.stop(true, true).fadeIn(ANIMATION_SPEED, onShowFinished);
                break;
            default:
                this.$element.show(onShowFinished);
                break;
        }

    }

    /**
     * Hide the modal, depending on the provided animation.
     */
    close() {
        this.scrollToPreviousPosition();

        this.$caller.attr('aria-expanded', false);
        this.$caller.attr('aria-haspopup', false);

        switch (this.animation) {
            case 'fade-in-out':
                this.$element.stop(true, true).fadeOut(ANIMATION_SPEED);
                break;
            default:
                this.$element.hide();
                break;
        }

        this.setFocus(this.$caller);
    }

    /**
     * Simply using .focus() doesn't suffice.
     *
     * @param $element jQuery.Element
     */
    setFocus($element) {
        $element.attr('tabindex', -1).on('blur focusout', () => {
            $element.removeAttr('tabindex');
        }).focus();
    }

    /**
     * Fancy menu opening animation:
     * - fades the modal in
     * - 'opens' it from the originating element
     * - fades in the content (otherwise it'll be resized)
     * - calls an optional callback
     *
     * For aesthetical reasons we have to animate to the previous height and not 100% max-height directly.
     */
    scaleFromOrigin(callBack) {
        this.$mainContent.css('opacity', 0);
        this.$element.show();

        let originalHeight = this.$mainWrapper.height();
        let box = this.$caller[0].getBoundingClientRect();
        this.$mainWrapper.css({
            'left': box.left,
            'width': box.width,
            'max-height': box.height,
            'top': box.top,
            'opacity': 0
        }).animate({
            'left': 0,
            'width': '100%',
            'max-height': originalHeight,
            'top': 0,
            'opacity': 1
        }, ANIMATION_SPEED, 'easeInOutSine', () => {
            // remove the scrollbars
            this.$mainWrapper.css({
                'max-height': '100%',
                'width': `calc(100% + ${scrollbarWidth}px)`,
                'margin-right': scrollbarWidth
            });
            this.$mainContent.animate({
                'opacity': 1
            }, ANIMATION_SPEED, callBack);
        });
    }

    /**
     * Prevent scrollable page when the modal is open.
     * We achieve this by setting the body to overflow: hidden and setting the height to 100%, thus
     * effectively cutting the rest of the page off. This scrolls to the top of the page, so we
     * also have to save the previous scroll state.
     *
     * We only do this if the modal covers the whole page and on mobile/tablet.
     */
    preventScrolling() {
        if (this.$mainContent.outerHeight() >= $(window).outerHeight() && (FefResponsiveHelper.isTablet() || FefResponsiveHelper.isSmartphone())) {
            this.previousScrollPosition = $(window).scrollTop();
            $('html').addClass('h-prevent-scrolling');
        }
    }

    /**
     * If, upon opening the modal, the ability to scroll was removed, we give it back now. This means:
     * - removing the class that prevents the scrolling
     * - scrolling back to the previously saved scroll position
     *
     * This makes it appear as if we never even scrolled away.
     */
    scrollToPreviousPosition() {
        if (this.previousScrollPosition !== null) {
            $('html').removeClass('h-prevent-scrolling');
            $(window).scrollTop(this.previousScrollPosition);
            this.previousScrollPosition = null;
        }
    }
}
<commit_msg>set focus into modal also on mobile devices<commit_after>import {DOM_CHANGED_EVENT} from '../classes/fef-dom-observer';
import {FefResponsiveHelper} from '../classes/fef-responsive-helper';

const ANIMATION_SPEED = 200;
const KEYCODES = {
    'enter': 13,
    'tab': 9,
    'escape': 27
};

let existingModals = {};
let scrollbarWidth = 0;

$(window).on(DOM_CHANGED_EVENT, (e) => {
    scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
    $('[data-modal-id]').each((index, element) => {

        $(element).on('click', () => {
            let $caller = $(element);
            let modalId = $caller.attr('data-modal-id');
            let $modalElement = $(`[data-id=${modalId}]`);

            if (existingModals[modalId]) {
                existingModals[modalId].show();
            } else if ($modalElement.length > 0) {
                existingModals[modalId] = new FefModal($modalElement, $caller);
            }
        });
    });
});

/**
 * Handles showing and hiding a modal element
 */
export class FefModal {

    /**
     * @param $element jQuery element
     * @param $caller jQuery element
     */
    constructor($element, $caller) {
        this.$element = $element;
        this.$caller = $caller;
        this.$focusTarget = this.$element.find('.js-focus-target').first();
        this.$mainWrapper = this.$element.find('.js-modal-main-wrapper');
        this.$mainContent = this.$element.find('.js-modal-main-content');
        this.animation = this.$element.attr('data-animation');
        this.previousScrollPosition = null;

        this.endOfModalClass = ".js-end-of-modal";
        $element.append('');

        this.bindEvents();

        if (this.$element.hasClass('js-min-height-of-masthead')) {
            this.$mainContent.css('min-height', $('.js-masthead').outerHeight());
        }

        this.show();
    }

    /**
     * Binds the relevant events for this modal:
     * - Click on a close-button or the overlay
     * - Pressing Escape
     */
    bindEvents() {
        this.$element.find('.js-close-modal, .js-modal-overlay').on('click', () => {
            this.close();
        });

        this.$element.on('keydown', (e) => {
            if (e.keyCode === KEYCODES.escape) {
                this.close();
            }
        });

        // A11Y Helper: when tabbing out of the modal --> on focus, close modal, set focus to the caller
        $(this.endOfModalClass).on('focus', () => {
            this.close();
            $(this.endOfModalClass).remove();
        });

    }

    /**
     * Show the modal, depending on the provided animation.
     */
    show() {
        const onShowFinished = () => {
            this.preventScrolling();

            if (this.$focusTarget.length === 1) {
                this.setFocus(this.$focusTarget);
            }
        };

        this.$caller.attr('aria-expanded', true);
        this.$caller.attr('aria-haspopup', true);

        switch (this.animation) {
            case 'scale-from-origin':
                this.scaleFromOrigin(onShowFinished);
                break;
            case 'fade-in-out':
                this.$element.stop(true, true).fadeIn(ANIMATION_SPEED, onShowFinished);
                break;
            default:
                this.$element.show(onShowFinished);
                break;
        }

    }

    /**
     * Hide the modal, depending on the provided animation.
     */
    close() {
        this.scrollToPreviousPosition();

        this.$caller.attr('aria-expanded', false);
        this.$caller.attr('aria-haspopup', false);

        switch (this.animation) {
            case 'fade-in-out':
                this.$element.stop(true, true).fadeOut(ANIMATION_SPEED);
                break;
            default:
                this.$element.hide();
                break;
        }

        this.setFocus(this.$caller);
    }

    /**
     * Simply using .focus() doesn't suffice.
     *
     * @param $element jQuery.Element
     */
    setFocus($element) {
        $element.attr('tabindex', -1).on('blur focusout', () => {
            $element.removeAttr('tabindex');
        }).focus();
    }

    /**
     * Fancy menu opening animation:
     * - fades the modal in
     * - 'opens' it from the originating element
     * - fades in the content (otherwise it'll be resized)
     * - calls an optional callback
     *
     * For aesthetical reasons we have to animate to the previous height and not 100% max-height directly.
     */
    scaleFromOrigin(callBack) {
        this.$mainContent.css('opacity', 0);
        this.$element.show();

        let originalHeight = this.$mainWrapper.height();
        let box = this.$caller[0].getBoundingClientRect();
        this.$mainWrapper.css({
            'left': box.left,
            'width': box.width,
            'max-height': box.height,
            'top': box.top,
            'opacity': 0
        }).animate({
            'left': 0,
            'width': '100%',
            'max-height': originalHeight,
            'top': 0,
            'opacity': 1
        }, ANIMATION_SPEED, 'easeInOutSine', () => {
            // remove the scrollbars
            this.$mainWrapper.css({
                'max-height': '100%',
                'width': `calc(100% + ${scrollbarWidth}px)`,
                'margin-right': scrollbarWidth
            });
            this.$mainContent.animate({
                'opacity': 1
            }, ANIMATION_SPEED, callBack);
        });
    }

    /**
     * Prevent scrollable page when the modal is open.
     * We achieve this by setting the body to overflow: hidden and setting the height to 100%, thus
     * effectively cutting the rest of the page off. This scrolls to the top of the page, so we
     * also have to save the previous scroll state.
     *
     * We only do this if the modal covers the whole page and on mobile/tablet.
     */
    preventScrolling() {
        if (this.$mainContent.outerHeight() >= $(window).outerHeight() && (FefResponsiveHelper.isTablet() || FefResponsiveHelper.isSmartphone())) {
            this.previousScrollPosition = $(window).scrollTop();
            $('html').addClass('h-prevent-scrolling');
        }
    }

    /**
     * If, upon opening the modal, the ability to scroll was removed, we give it back now. This means:
     * - removing the class that prevents the scrolling
     * - scrolling back to the previously saved scroll position
     *
     * This makes it appear as if we never even scrolled away.
     */
    scrollToPreviousPosition() {
        if (this.previousScrollPosition !== null) {
            $('html').removeClass('h-prevent-scrolling');
            $(window).scrollTop(this.previousScrollPosition);
            this.previousScrollPosition = null;
        }
    }
}
<|endoftext|>


<commit_before>import { useMemo } from 'react';

export default function useClassName(className) {
    return useMemo(() => {
        if (typeof className === 'string') {
            const [base] = className.split(' ');
            const bem = (k) => `${base}__${k}`;

            return {
                container: className,
                value: bem('value'),
                input: bem('input'),
                select: bem('select'),
                options: bem('options'),
                option: bem('option'),
                group: bem('group'),
                'group-header': bem('group-header'),
                'is-selected': 'is-selected',
                'is-highlighted': 'is-highlighted',
                'is-loading': 'is-loading',
                'has-focus': 'has-focus',
            };
        }

        return className;
    }, [className]);
}
<commit_msg>Bugfix<commit_after>import { useMemo } from 'react';

export default function useClassName(className) {
    return useMemo(() => {
        if (typeof className === 'string') {
            const [base] = className.split(' ');
            const bem = (k) => `${base}__${k}`;

            return {
                container: className,
                value: bem('value'),
                input: bem('input'),
                select: bem('select'),
                options: bem('options'),
                option: bem('option'),
                row: bem('row'),
                group: bem('group'),
                'group-header': bem('group-header'),
                'is-selected': 'is-selected',
                'is-highlighted': 'is-highlighted',
                'is-loading': 'is-loading',
                'is-disabled': 'is-disabled',
                'has-focus': 'has-focus',
            };
        }

        return className;
    }, [className]);
}
<|endoftext|>


<commit_before>/**
 * jQuery Stepy - A Wizard Plugin - http://wbotelhos.com/stepy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Stepy is a plugin based on FormToWizard that generates a customizable wizard.
 *
 * Licensed under The MIT License
 *
 * @version			0.4.0
 * @since			07.03.2010
 * @author			Washington Botelho dos Santos
 * @documentation	http://wbotelhos.com/raty
 * @twitter			http://twitter.com/wbotelhos
 * @license			http://opensource.org/licenses/mit-license.php MIT
 * @package			jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#default').stepy();
 *
 *	<form id="default">
 *		<fieldset title="Step 1">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		<fieldset title="Step 2">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		// and so on..
 *
 *		<input type="submit" class="finish" value="Finish!"/>
 *	</form>
 *
 */

;(function($) {

	$.fn.stepy = function(settings) {

		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.stepy.apply($(this), [settings]);
			});
		}

		var opt			= $.extend({}, $.fn.stepy.defaults, settings),
			$this		= $(this),
			id			= this.attr('id'),
			steps		= $this.children('fieldset'),
			size		= steps.size(),
			description	= '',
			title		= '',
			step,
			$titles		= $('
', { id: id + '-titles', 'class': 'stepy-titles' }).insertBefore($this);

		if (id === undefined) {
			id = 'stepy-' + $this.index();
			$this.attr('id', id);
		}

        if (opt.validate && $this.is('form')) {
        	$this.append('
');
        }

        steps.each(function(index) { // fieldset
        	step = $(this);

        	step
        	.attr('id', id + '-step-' + index)
        	.addClass('step')
        	.append('
');

        	title = (step.attr('title') != '') ? step.attr('title') : '--';

        	description = step.children('legend:first').html();

        	$titles.append('
' + title  + '' + description + '
');

        	if (index == 0) {
        		if (size > 1) {
        			createNextButton(index);
        		}
        	} else {
        		createBackButton(index);
        		step.hide();

        		if (index < size - 1) {
	        		createNextButton(index);
	        	}
        	}
        });

        $titles.children('li:first').addClass('current-step');

        var finish = $this.children('.finish');

		if (opt.finish) {
	        if (finish.length) {
        		finish.hide().click(function() {
        			validate($this, size - 1, opt);
        		})
        		.appendTo($this.find('p:last'));
	        } else if ($this.is('form')) {
	        	debug('Button with class "finish" missing!');
	        }
        }

        if (opt.titleClick) {
        	$titles.children().click(function() {
        		var clicked = parseInt($(this).attr('id').split('-')[2]),
        			current = parseInt($titles.children('.current-step').attr('id').split('-')[2]),
        			maxStep = clicked;

				if (clicked > current) {									// Validate only clickeds steps ahead.
					if (isStopCallback(opt.onNext, clicked)) {
						return;
					}

					maxStep = getMaxStep($this, opt, clicked);
				} else if (clicked < current) {
					if (isStopCallback(opt.onBack, clicked)) {
						return;
					}
				}

				if (clicked != current) {									// Avoid change to the same step.
					selectStep($this, maxStep);

			        if (opt.finish && maxStep + 1 == size) {
	                	finish.show();
	                }
				}
        	});
    	} else {
    		$titles.children().css('cursor', 'default');
    	}

        steps.delegate('input[type="text"], input[type="password"]', 'keypress', function(evt) {
        	var key = (evt.keyCode ? evt.keyCode : evt.which);

        	if (key == 13) {
        		evt.preventDefault();

        		var buttons = $(this).parent().children('p.' + id + '-buttons');

        		if (buttons.length) {
        			var next = buttons.children('a.button-next');

        			if (next.length) {
        				next.click();
        			} else {
	        			var finish = buttons.children('input.finish');

	        			if (finish.length) {
	        				finish.click();
	        			}
        			}
        		}
        	}
        });

        function isStopCallback(callback, clicked) {
        	var isValid = callback.apply($this, [clicked + 1]);

        	return !(isValid || isValid === undefined);
        };

        function createBackButton(index) {
        	$('', {
        		id:			id + '-back-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-back',
        		html:		opt.backLabel
        	}).click(function() {
        		if (!isStopCallback(opt.onBack, index - 1)) {
	                selectStep($this, index - 1);

	                if (index + 1 == size) {
	                	finish.hide();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function createNextButton(index) {
        	$('', {
        		id:			id + '-next-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-next',
        		html:		opt.nextLabel
        	}).click(function() {
        		if (!isStopCallback(opt.onNext, index + 1)) {
	        		var maxStep	= getMaxStep($this, opt, index + 1);

					selectStep($this, maxStep);

			        if (opt.finish && maxStep + 1 == size) {
	                	finish.show();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function getMaxStep(context, opt, clicked) {
        	var maxStep = clicked,
        		isValid = true;

        	if (opt.validate) {
	        	for (var i = 0; i < clicked; i++) {
					isValid = validate($this, i, opt) && isValid;	// Accumulates validations.

					if (opt.block && !isValid) {					// In the first invalid step the function stops or not.
						maxStep = i;								// The first invalid step.
						break;
					}
				}
        	}

        	return maxStep;
        };

		return $this;
	};

	function selectStep(context, index) {
		var id		= context.attr('id'),
			size	= context.children('fieldset').size(),
			step;

		if (index > size - 1) {
			index = size - 1;
		}

		context
			.children('fieldset').hide()
		.end()
			.children('fieldset')
				.eq(index).show();

		context
			.prev()
				.children().removeClass('current-step')
			.end()
			.children()
				.eq(index).addClass('current-step');

        if (context.is('form')) {
        	context
        		.children('fieldset')
	        		.eq(index)
		        		.find(':input:visible')
			        		.each(function() {
			        			step = $(this);

					        	if (!step.attr('disabled')) {
					    			step.focus();
					    			return false;
					        	}
			        		});
        }
	};

    function validate(context, index, opt) {
    	if (!context.is('form')) {
    		return true;
    	}

    	var id		= context.attr('id'),
    		isValid	= true,
    		step	= context.children('fieldset').eq(index),
    		titles	= context.prev('ul.stepy-titles').children();

    	step.find(':input').each(function() {
    		isValid = isValid && context.validate().element($(this));

    		if (isValid === undefined) {
    			isValid = true;
    		}

    		if (isValid) {
    			if (opt.errorImage) {
    				titles.eq(index).removeClass('error-image');
    			}
    		} else {
    			if (opt.block) {
    				selectStep(context, index);
    			}

    			if (opt.errorImage) {
    				titles.eq(index).addClass('error-image');
    			}

    			context.validate().focusInvalid();
    		}
    	});

    	return isValid;
    };

    $.fn.stepy.step = function(index, idOrClass) {
    	var context = getContext(index, idOrClass, 'step');

		if (idOrClass.indexOf('.') >= 0) {
			return;
		}

		selectStep(context, index - 1);

		$.fn.stepy;
	};

    function getContext(value, idOrClass, name) {
		var context = undefined;

		if (idOrClass == undefined) {
			debug('Specify an ID or class to be the target of the action.');
			return;
		}

		if (idOrClass) {
			if (idOrClass.indexOf('.') >= 0) {
				var idEach;

				return $(idOrClass).each(function() {
					idEach = '#' + $(this).attr('id');

					if (name == 'step') {
						$.fn.stepy.step(value, idEach);
					}
				});
			}

			context = $(idOrClass);

			if (!context.length) {
				debug('"' + idOrClass + '" is a invalid identifier for the public funtion $.fn.stepy.' + name + '().');
				return;
			}
		}

		return context;
	};

    function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.stepy.defaults = {
		backLabel:		'< Back',
		block:			false,
		errorImage:		false,
		finish:			true,
		onBack:			function(index) { return true; },
		onNext:			function(index) { return true; },
		nextLabel:		'Next >',
		titleClick:		false,
		validate:		false
	};

})(jQuery);<commit_msg>Changed the ID example.<commit_after>/**
 * jQuery Stepy - A Wizard Plugin - http://wbotelhos.com/stepy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Stepy is a plugin based on FormToWizard that generates a customizable wizard.
 *
 * Licensed under The MIT License
 *
 * @version			0.4.0
 * @since			07.03.2010
 * @author			Washington Botelho dos Santos
 * @documentation	http://wbotelhos.com/raty
 * @twitter			http://twitter.com/wbotelhos
 * @license			http://opensource.org/licenses/mit-license.php MIT
 * @package			jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#stepy').stepy();
 *
 *	<form id="stepy">
 *		<fieldset title="Step 1">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		<fieldset title="Step 2">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		// and so on..
 *
 *		<input type="submit" class="finish" value="Finish!"/>
 *	</form>
 *
 */

;(function($) {

	$.fn.stepy = function(settings) {

		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.stepy.apply($(this), [settings]);
			});
		}

		var opt			= $.extend({}, $.fn.stepy.defaults, settings),
			$this		= $(this),
			id			= this.attr('id'),
			steps		= $this.children('fieldset'),
			size		= steps.size(),
			description	= '',
			title		= '',
			step,
			$titles		= $('
', { id: id + '-titles', 'class': 'stepy-titles' }).insertBefore($this);

		if (id === undefined) {
			id = 'stepy-' + $this.index();
			$this.attr('id', id);
		}

        if (opt.validate && $this.is('form')) {
        	$this.append('
');
        }

        steps.each(function(index) { // fieldset
        	step = $(this);

        	step
        	.attr('id', id + '-step-' + index)
        	.addClass('step')
        	.append('
');

        	title = (step.attr('title') != '') ? step.attr('title') : '--';

        	description = step.children('legend:first').html();

        	$titles.append('
' + title  + '' + description + '
');

        	if (index == 0) {
        		if (size > 1) {
        			createNextButton(index);
        		}
        	} else {
        		createBackButton(index);
        		step.hide();

        		if (index < size - 1) {
	        		createNextButton(index);
	        	}
        	}
        });

        $titles.children('li:first').addClass('current-step');

        var finish = $this.children('.finish');

		if (opt.finish) {
	        if (finish.length) {
        		finish.hide().click(function() {
        			validate($this, size - 1, opt);
        		})
        		.appendTo($this.find('p:last'));
	        } else if ($this.is('form')) {
	        	debug('Button with class "finish" missing!');
	        }
        }

        if (opt.titleClick) {
        	$titles.children().click(function() {
        		var clicked = parseInt($(this).attr('id').split('-')[2]),
        			current = parseInt($titles.children('.current-step').attr('id').split('-')[2]),
        			maxStep = clicked;

				if (clicked > current) {									// Validate only clickeds steps ahead.
					if (isStopCallback(opt.onNext, clicked)) {
						return;
					}

					maxStep = getMaxStep($this, opt, clicked);
				} else if (clicked < current) {
					if (isStopCallback(opt.onBack, clicked)) {
						return;
					}
				}

				if (clicked != current) {									// Avoid change to the same step.
					selectStep($this, maxStep);

			        if (opt.finish && maxStep + 1 == size) {
	                	finish.show();
	                }
				}
        	});
    	} else {
    		$titles.children().css('cursor', 'default');
    	}

        steps.delegate('input[type="text"], input[type="password"]', 'keypress', function(evt) {
        	var key = (evt.keyCode ? evt.keyCode : evt.which);

        	if (key == 13) {
        		evt.preventDefault();

        		var buttons = $(this).parent().children('p.' + id + '-buttons');

        		if (buttons.length) {
        			var next = buttons.children('a.button-next');

        			if (next.length) {
        				next.click();
        			} else {
	        			var finish = buttons.children('input.finish');

	        			if (finish.length) {
	        				finish.click();
	        			}
        			}
        		}
        	}
        });

        function isStopCallback(callback, clicked) {
        	var isValid = callback.apply($this, [clicked + 1]);

        	return !(isValid || isValid === undefined);
        };

        function createBackButton(index) {
        	$('', {
        		id:			id + '-back-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-back',
        		html:		opt.backLabel
        	}).click(function() {
        		if (!isStopCallback(opt.onBack, index - 1)) {
	                selectStep($this, index - 1);

	                if (index + 1 == size) {
	                	finish.hide();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function createNextButton(index) {
        	$('', {
        		id:			id + '-next-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-next',
        		html:		opt.nextLabel
        	}).click(function() {
        		if (!isStopCallback(opt.onNext, index + 1)) {
	        		var maxStep	= getMaxStep($this, opt, index + 1);

					selectStep($this, maxStep);

			        if (opt.finish && maxStep + 1 == size) {
	                	finish.show();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function getMaxStep(context, opt, clicked) {
        	var maxStep = clicked,
        		isValid = true;

        	if (opt.validate) {
	        	for (var i = 0; i < clicked; i++) {
					isValid = validate($this, i, opt) && isValid;	// Accumulates validations.

					if (opt.block && !isValid) {					// In the first invalid step the function stops or not.
						maxStep = i;								// The first invalid step.
						break;
					}
				}
        	}

        	return maxStep;
        };

		return $this;
	};

	function selectStep(context, index) {
		var id		= context.attr('id'),
			size	= context.children('fieldset').size(),
			step;

		if (index > size - 1) {
			index = size - 1;
		}

		context
			.children('fieldset').hide()
		.end()
			.children('fieldset')
				.eq(index).show();

		context
			.prev()
				.children().removeClass('current-step')
			.end()
			.children()
				.eq(index).addClass('current-step');

        if (context.is('form')) {
        	context
        		.children('fieldset')
	        		.eq(index)
		        		.find(':input:visible')
			        		.each(function() {
			        			step = $(this);

					        	if (!step.attr('disabled')) {
					    			step.focus();
					    			return false;
					        	}
			        		});
        }
	};

    function validate(context, index, opt) {
    	if (!context.is('form')) {
    		return true;
    	}

    	var id		= context.attr('id'),
    		isValid	= true,
    		step	= context.children('fieldset').eq(index),
    		titles	= context.prev('ul.stepy-titles').children();

    	step.find(':input').each(function() {
    		isValid = isValid && context.validate().element($(this));

    		if (isValid === undefined) {
    			isValid = true;
    		}

    		if (isValid) {
    			if (opt.errorImage) {
    				titles.eq(index).removeClass('error-image');
    			}
    		} else {
    			if (opt.block) {
    				selectStep(context, index);
    			}

    			if (opt.errorImage) {
    				titles.eq(index).addClass('error-image');
    			}

    			context.validate().focusInvalid();
    		}
    	});

    	return isValid;
    };

    $.fn.stepy.step = function(index, idOrClass) {
    	var context = getContext(index, idOrClass, 'step');

		if (idOrClass.indexOf('.') >= 0) {
			return;
		}

		selectStep(context, index - 1);

		$.fn.stepy;
	};

    function getContext(value, idOrClass, name) {
		var context = undefined;

		if (idOrClass == undefined) {
			debug('Specify an ID or class to be the target of the action.');
			return;
		}

		if (idOrClass) {
			if (idOrClass.indexOf('.') >= 0) {
				var idEach;

				return $(idOrClass).each(function() {
					idEach = '#' + $(this).attr('id');

					if (name == 'step') {
						$.fn.stepy.step(value, idEach);
					}
				});
			}

			context = $(idOrClass);

			if (!context.length) {
				debug('"' + idOrClass + '" is a invalid identifier for the public funtion $.fn.stepy.' + name + '().');
				return;
			}
		}

		return context;
	};

    function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.stepy.defaults = {
		backLabel:		'< Back',
		block:			false,
		errorImage:		false,
		finish:			true,
		onBack:			function(index) { return true; },
		onNext:			function(index) { return true; },
		nextLabel:		'Next >',
		titleClick:		false,
		validate:		false
	};

})(jQuery);<|endoftext|>


<commit_before>/**
 * jQuery Stepy - A Wizard Plugin - http://wbotelhos.com/stepy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Stepy is a plugin based on FormToWizard that generates a customizable wizard.
 *
 * Licensed under The MIT License
 *
 * @version			0.5.0
 * @since			07.03.2010
 * @author			Washington Botelho dos Santos
 * @documentation	http://wbotelhos.com/raty
 * @twitter			http://twitter.com/wbotelhos
 * @license			http://opensource.org/licenses/mit-license.php MIT
 * @package			jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#stepy').stepy();
 *
 *	<form id="stepy">
 *		<fieldset title="Step 1">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		<fieldset title="Step 2">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		// and so on..
 *
 *		<input type="submit" class="finish" value="Finish!"/>
 *	</form>
 *
 */

;(function($) {

	$.fn.stepy = function(settings) {

		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.stepy.apply($(this), [settings]);
			});
		}

		var opt				= $.extend({}, $.fn.stepy.defaults, settings),
			$this			= $(this),
			id				= this.attr('id'),
			$steps			= $this.children('fieldset'),
			size			= $steps.size(),
			$titlesWrapper	= $('
', { id: id + '-titles', 'class': 'stepy-titles' }),
			description		= '',
			title			= '',
			$legend			= null,
			hasLegend		= true,
			isForm			= $this.is('form'),
			onSubmit		= '',
			step;

		if (opt.titleTarget) {
			$(opt.titleTarget).html($titlesWrapper);
		} else {
			$titlesWrapper.insertBefore($this);
		}

		if (id === undefined) {
			id = 'stepy-' + $this.index();
			$this.attr('id', id);
		}

		$this.data('options', opt);

        if (opt.validate) {
        	$this.append('
');
        }

        $steps.each(function(index) {
        	step = $(this);

        	step
        	.attr('id', id + '-step-' + index)
        	.addClass('step')
        	.append('
');

        	title = (step.attr('title') != '') ? step.attr('title') : '--';

        	$legend = step.children('legend');

        	if (!opt.legend) {
        		$legend.hide();
        	}

        	description = '';

        	if (opt.description) {
        		if ($legend.length) {
        			description = '' + $legend.html() + '';
        		} else {
        			debug(id + ': the legend element of the step ' + (index + 1) + ' is required to set the description!');
        			hasLegend = false;
        		}
        	}

        	$titlesWrapper.append('
' + title  + description + '
');

        	if (index == 0) {
        		if (size > 1) {
        			createNextButton(index);
        		}
        	} else {
        		createBackButton(index);
        		step.hide();

        		if (index < size - 1) {
	        		createNextButton(index);
	        	}
        	}
        });

        var $titles	= $titlesWrapper.children(),
        	finish	= $this.children('.finish');

        $titles.first().addClass('current-step');

		if (opt.finishButton) {
	        if (finish.length) {
	        	if (opt.finish && isForm) {
	        		onSubmit = $this.attr('onsubmit');
	        		$this.attr('onsubmit', 'return false;');
	        	}

        		finish.click(function(evt) {
        			var isSubmit = finish.attr('type') == 'submit';

    				if (opt.finish && isStopCallback(opt.finish, size - 1)) {
   						evt.preventDefault();
    				} else {
    					if (isForm) {
    						if (onSubmit) {
    							$this.attr('onsubmit', onSubmit);
    						} else {
    							$this.removeAttr('onsubmit');
    						}

    						if (!isSubmit && (!opt.validate || validate($this, size - 1, opt))) {
    							$this.submit();
    						}
    					}
    				}
        		});

        		if (size > 1) {
        			finish.hide();
                }

        		finish.appendTo($this.find('p:last'));
	        } else {
	        	debug(id + ': element with class name "finish" missing!');
	        }
        }

        if (opt.titleClick) {
        	$titles.click(function() {
        		var	currentArray	= $titlesWrapper.children('.current-step').attr('id').split('-'),
	        		current			= parseInt(currentArray[currentArray.length - 1]),
	        		clicked			= $(this).index(),
        			maxStep			= clicked;

				if (clicked > current) {									// Validate only clickeds steps ahead.
					if (opt.next && isStopCallback(opt.next, clicked)) {
						return;
					}

					maxStep = getMaxStep($this, opt, clicked);
				} else if (clicked < current) {
					if (opt.back && isStopCallback(opt.back, clicked)) {
						return;
					}
				}

				if (clicked != current) {									// Avoid change to the same step.
					selectStep($this, maxStep);

			        if (opt.finishButton && maxStep + 1 == size) {
	                	finish.show();
	                }
				}
        	});
    	} else {
    		$titles.css('cursor', 'default');
    	}

        $steps.delegate('input[type="text"], input[type="password"]', 'keypress', function(evt) {
        	var key = (evt.keyCode ? evt.keyCode : evt.which);

        	if (key == 13) {
        		evt.preventDefault();

        		var buttons = $(this).parent().children('p.' + id + '-buttons');

        		if (buttons.length) {
        			var next = buttons.children('a.button-next');

        			if (next.length) {
        				next.click();
        			} else {
	        			var finish = buttons.children('input.finish');

	        			if (finish.length) {
	        				finish.click();
	        			}
        			}
        		}
        	}
        });

        function isStopCallback(callback, clicked) {
        	var isValid = callback.apply($this, [clicked + 1]);

        	return !(isValid || isValid === undefined);
        };

        function createBackButton(index) {
        	$('', {
        		id:			id + '-back-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-back',
        		html:		opt.backLabel
        	}).click(function() {
        		if (!opt.back || !isStopCallback(opt.back, index - 1)) {
	                selectStep($this, index - 1);

	                if (index + 1 == size) {
	                	finish.hide();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function createNextButton(index) {
        	$('', {
        		id:			id + '-next-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-next',
        		html:		opt.nextLabel
        	}).click(function() {
        		if (!opt.next || !isStopCallback(opt.next, index + 1)) {
	        		var maxStep	= getMaxStep($this, opt, index + 1);

					selectStep($this, maxStep);

			        if (opt.finishButton && maxStep + 1 == size) {
	                	finish.show();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function getMaxStep(context, opt, clicked) {
        	var maxStep = clicked,
        		isValid = true;

        	if (opt.validate) {
	        	for (var i = 0; i < clicked; i++) {
					isValid = validate($this, i, opt) && isValid;	// Accumulates validations.

					if (opt.block && !isValid) {					// In the first invalid step the function stops or not.
						maxStep = i;								// The first invalid step.
						break;
					}
				}
        	}

        	return maxStep;
        };

		return $this;
	};

	function selectStep(context, index) {
		var id		= context.attr('id'),
			$steps	= context.children('fieldset'),
			size	= $steps.size(),
			$titles	= $('ul#' + id + '-titles').children(),
			step;

		if (index > size - 1) {
			index = size - 1;
		}

		$steps.hide().eq(index).show();

		$titles.removeClass('current-step').eq(index).addClass('current-step');

        if (context.is('form')) {
        	$steps
        		.eq(index)
	        		.find(':input:visible')
		        		.each(function() {
		        			step = $(this);

				        	if (!step.attr('disabled')) { // TODO: is
				    			step.focus();
				    			return false;
				        	}
		        		});
        }
	};

	function validate(context, index, opt) {
		if (!context.is('form')) {
			return true;
		}

		var id		= context.attr('id'),
			isValid	= true,
			$step	= context.children('fieldset').eq(index),
			$title	= $('ul#' + id + '-titles').children().eq(index);

		$step.find(':input').each(function() {
			isValid &= context.validate().element($(this));

			if (isValid === undefined) {
				isValid = true;
			}

			if (isValid) {
				if (opt.errorImage) {
					$title.removeClass('error-image');
				}
			} else {
				if (opt.block) {
					selectStep(context, index);
				}

				if (opt.errorImage) {
					$title.addClass('error-image');
				}

				context.validate().focusInvalid();
			}
		});

		return isValid;
	};

    $.fn.stepy.step = function(index, idOrClass) {
    	var context = getContext(index, idOrClass, 'step');

		if (idOrClass.indexOf('.') >= 0) {
			return;
		}

		selectStep(context, index - 1);

		$.fn.stepy;
	};

    function getContext(value, idOrClass, name) {
		var context = undefined;

		if (idOrClass == undefined) {
			debug('Specify an ID or class to be the target of the action.');
			return;
		}

		if (idOrClass) {
			if (idOrClass.indexOf('.') >= 0) {
				var idEach;

				return $(idOrClass).each(function() {
					idEach = '#' + $(this).attr('id');

					if (name == 'step') {
						$.fn.stepy.step(value, idEach);
					}
				});
			}

			context = $(idOrClass);

			if (!context.length) {
				debug('"' + idOrClass + '" is a invalid identifier for the public funtion $.fn.stepy.' + name + '().');
				return;
			}
		}

		return context;
	};

    function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.stepy.defaults = {
		back:			null,
		backLabel:		'< Back',
		block:			false,
		description:	true,
		errorImage:		false,
		finish:			null,
		finishButton:	true,
		legend:			true,
		next:			null,
		nextLabel:		'Next >',
		titleClick:		false,
		titleTarget:	'',
		validate:		false
	};

})(jQuery);<commit_msg>Refactored the selection of the first field.<commit_after>/**
 * jQuery Stepy - A Wizard Plugin - http://wbotelhos.com/stepy
 * ---------------------------------------------------------------------------------
 *
 * jQuery Stepy is a plugin based on FormToWizard that generates a customizable wizard.
 *
 * Licensed under The MIT License
 *
 * @version			0.5.0
 * @since			07.03.2010
 * @author			Washington Botelho dos Santos
 * @documentation	http://wbotelhos.com/raty
 * @twitter			http://twitter.com/wbotelhos
 * @license			http://opensource.org/licenses/mit-license.php MIT
 * @package			jQuery Plugins
 *
 * Usage with default values:
 * ---------------------------------------------------------------------------------
 * $('#stepy').stepy();
 *
 *	<form id="stepy">
 *		<fieldset title="Step 1">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		<fieldset title="Step 2">
 *			<legend>description one</legend>
 *
 *		</fieldset>
 *
 *		// and so on..
 *
 *		<input type="submit" class="finish" value="Finish!"/>
 *	</form>
 *
 */

;(function($) {

	$.fn.stepy = function(settings) {

		if (this.length == 0) {
			debug('Selector invalid or missing!');
			return;
		} else if (this.length > 1) {
			return this.each(function() {
				$.fn.stepy.apply($(this), [settings]);
			});
		}

		var opt				= $.extend({}, $.fn.stepy.defaults, settings),
			$this			= $(this),
			id				= this.attr('id'),
			$steps			= $this.children('fieldset'),
			size			= $steps.size(),
			$titlesWrapper	= $('
', { id: id + '-titles', 'class': 'stepy-titles' }),
			description		= '',
			title			= '',
			$legend			= null,
			hasLegend		= true,
			isForm			= $this.is('form'),
			onSubmit		= '',
			step;

		if (opt.titleTarget) {
			$(opt.titleTarget).html($titlesWrapper);
		} else {
			$titlesWrapper.insertBefore($this);
		}

		if (id === undefined) {
			id = 'stepy-' + $this.index();
			$this.attr('id', id);
		}

		$this.data('options', opt);

        if (opt.validate) {
        	$this.append('
');
        }

        $steps.each(function(index) {
        	step = $(this);

        	step
        	.attr('id', id + '-step-' + index)
        	.addClass('step')
        	.append('
');

        	title = (step.attr('title') != '') ? step.attr('title') : '--';

        	$legend = step.children('legend');

        	if (!opt.legend) {
        		$legend.hide();
        	}

        	description = '';

        	if (opt.description) {
        		if ($legend.length) {
        			description = '' + $legend.html() + '';
        		} else {
        			debug(id + ': the legend element of the step ' + (index + 1) + ' is required to set the description!');
        			hasLegend = false;
        		}
        	}

        	$titlesWrapper.append('
' + title  + description + '
');

        	if (index == 0) {
        		if (size > 1) {
        			createNextButton(index);
        		}
        	} else {
        		createBackButton(index);
        		step.hide();

        		if (index < size - 1) {
	        		createNextButton(index);
	        	}
        	}
        });

        var $titles	= $titlesWrapper.children(),
        	finish	= $this.children('.finish');

        $titles.first().addClass('current-step');

		if (opt.finishButton) {
	        if (finish.length) {
	        	if (opt.finish && isForm) {
	        		onSubmit = $this.attr('onsubmit');
	        		$this.attr('onsubmit', 'return false;');
	        	}

        		finish.click(function(evt) {
        			var isSubmit = finish.attr('type') == 'submit';

    				if (opt.finish && isStopCallback(opt.finish, size - 1)) {
   						evt.preventDefault();
    				} else {
    					if (isForm) {
    						if (onSubmit) {
    							$this.attr('onsubmit', onSubmit);
    						} else {
    							$this.removeAttr('onsubmit');
    						}

    						if (!isSubmit && (!opt.validate || validate($this, size - 1, opt))) {
    							$this.submit();
    						}
    					}
    				}
        		});

        		if (size > 1) {
        			finish.hide();
                }

        		finish.appendTo($this.find('p:last'));
	        } else {
	        	debug(id + ': element with class name "finish" missing!');
	        }
        }

        if (opt.titleClick) {
        	$titles.click(function() {
        		var	currentArray	= $titlesWrapper.children('.current-step').attr('id').split('-'),
	        		current			= parseInt(currentArray[currentArray.length - 1]),
	        		clicked			= $(this).index(),
        			maxStep			= clicked;

				if (clicked > current) {									// Validate only clickeds steps ahead.
					if (opt.next && isStopCallback(opt.next, clicked)) {
						return;
					}

					maxStep = getMaxStep($this, opt, clicked);
				} else if (clicked < current) {
					if (opt.back && isStopCallback(opt.back, clicked)) {
						return;
					}
				}

				if (clicked != current) {									// Avoid change to the same step.
					selectStep($this, maxStep);

			        if (opt.finishButton && maxStep + 1 == size) {
	                	finish.show();
	                }
				}
        	});
    	} else {
    		$titles.css('cursor', 'default');
    	}

        $steps.delegate('input[type="text"], input[type="password"]', 'keypress', function(evt) {
        	var key = (evt.keyCode ? evt.keyCode : evt.which);

        	if (key == 13) {
        		evt.preventDefault();

        		var buttons = $(this).parent().children('p.' + id + '-buttons');

        		if (buttons.length) {
        			var next = buttons.children('a.button-next');

        			if (next.length) {
        				next.click();
        			} else {
	        			var finish = buttons.children('input.finish');

	        			if (finish.length) {
	        				finish.click();
	        			}
        			}
        		}
        	}
        });

        function isStopCallback(callback, clicked) {
        	var isValid = callback.apply($this, [clicked + 1]);

        	return !(isValid || isValid === undefined);
        };

        function createBackButton(index) {
        	$('', {
        		id:			id + '-back-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-back',
        		html:		opt.backLabel
        	}).click(function() {
        		if (!opt.back || !isStopCallback(opt.back, index - 1)) {
	                selectStep($this, index - 1);

	                if (index + 1 == size) {
	                	finish.hide();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function createNextButton(index) {
        	$('', {
        		id:			id + '-next-' + index,
        		href:		'javascript:void(0);',
        		'class':	'button-next',
        		html:		opt.nextLabel
        	}).click(function() {
        		if (!opt.next || !isStopCallback(opt.next, index + 1)) {
	        		var maxStep	= getMaxStep($this, opt, index + 1);

					selectStep($this, maxStep);

			        if (opt.finishButton && maxStep + 1 == size) {
	                	finish.show();
	                }
        		}
            })
            .appendTo($('p#' + id + '-buttons-' + index));
        };

        function getMaxStep(context, opt, clicked) {
        	var maxStep = clicked,
        		isValid = true;

        	if (opt.validate) {
	        	for (var i = 0; i < clicked; i++) {
					isValid = validate($this, i, opt) && isValid;	// Accumulates validations.

					if (opt.block && !isValid) {					// In the first invalid step the function stops or not.
						maxStep = i;								// The first invalid step.
						break;
					}
				}
        	}

        	return maxStep;
        };

		return $this;
	};

	function selectStep(context, index) {
		var id		= context.attr('id'),
			$steps	= context.children('fieldset'),
			size	= $steps.size(),
			$titles	= $('ul#' + id + '-titles').children(),
			step;

		if (index > size - 1) {
			index = size - 1;
		}

		$steps.hide().eq(index).show();

		$titles.removeClass('current-step').eq(index).addClass('current-step');

        if (context.is('form')) {
        	$steps.eq(index).find(':input:visible:enabled:first').focus();
        }
	};

	function validate(context, index, opt) {
		if (!context.is('form')) {
			return true;
		}

		var id		= context.attr('id'),
			isValid	= true,
			$step	= context.children('fieldset').eq(index),
			$title	= $('ul#' + id + '-titles').children().eq(index);

		$step.find(':input').each(function() {
			isValid &= context.validate().element($(this));

			if (isValid === undefined) {
				isValid = true;
			}

			if (isValid) {
				if (opt.errorImage) {
					$title.removeClass('error-image');
				}
			} else {
				if (opt.block) {
					selectStep(context, index);
				}

				if (opt.errorImage) {
					$title.addClass('error-image');
				}

				context.validate().focusInvalid();
			}
		});

		return isValid;
	};

    $.fn.stepy.step = function(index, idOrClass) {
    	var context = getContext(index, idOrClass, 'step');

		if (idOrClass.indexOf('.') >= 0) {
			return;
		}

		selectStep(context, index - 1);

		$.fn.stepy;
	};

    function getContext(value, idOrClass, name) {
		var context = undefined;

		if (idOrClass == undefined) {
			debug('Specify an ID or class to be the target of the action.');
			return;
		}

		if (idOrClass) {
			if (idOrClass.indexOf('.') >= 0) {
				var idEach;

				return $(idOrClass).each(function() {
					idEach = '#' + $(this).attr('id');

					if (name == 'step') {
						$.fn.stepy.step(value, idEach);
					}
				});
			}

			context = $(idOrClass);

			if (!context.length) {
				debug('"' + idOrClass + '" is a invalid identifier for the public funtion $.fn.stepy.' + name + '().');
				return;
			}
		}

		return context;
	};

    function debug(message) {
		if (window.console && window.console.log) {
			window.console.log(message);
		}
	};

	$.fn.stepy.defaults = {
		back:			null,
		backLabel:		'< Back',
		block:			false,
		description:	true,
		errorImage:		false,
		finish:			null,
		finishButton:	true,
		legend:			true,
		next:			null,
		nextLabel:		'Next >',
		titleClick:		false,
		titleTarget:	'',
		validate:		false
	};

})(jQuery);<|endoftext|>


<commit_before>'use strict';

function checkAll(){
    var checkboxes = document.getElementsByTagName('input');
    for(var checkbox in checkboxes){
        if(checkboxes[checkbox].type === 'checkbox'){
            checkboxes[checkbox].checked = true;
        }
    }
}

function check_checkbox(id){
    if(!document.body.contains(document.getElementById(id))){
        return;
    }

    document.getElementById('checkbox-' + id).checked =
      !document.getElementById('checkbox-' + id).checked;
}

function clearAll(){
    if(!window.confirm('window.localStorage.clear()?')){
        return;
    }

    window.localStorage.clear();
    refresh();
}

function headers(){
    document.getElementById('result').innerHTML =
      '
'
        + 'Key <input onclick=refresh() type=button value="Refresh [R]">'
          + '<input onclick=checkAll() type=button value=checkAll()>'
          + '<input onclick=removeChecked() type=button value=removeChecked()>'
        + '<input onclick=removeAll() type=button value=removeAll()>'
          + '<input onclick=clearAll() type=button value=window.localStorage.clear()>'
          + ' Value'
      + document.getElementById('result').innerHTML;
}

function refresh(){
    var output = '';

    for(var key in window.localStorage){
        output += '
<input disabled id="checkbox-'
          + key
          + '" type=checkbox><input onclick="removeItem(\'' + key + '\', this)" type=button value=X> '
          + key
          + ''
          + window.localStorage.getItem(key);
    }

    document.getElementById('result').innerHTML = output;

    headers();
}

function removeAll(){
    if(!window.confirm('removeAll()?')){
        return;
    }

    for(var key in window.localStorage){
        window.localStorage.removeItem(key);
    }

    document.getElementById('result').innerHTML = '';
    headers();
}

function removeChecked(){
    if(!window.confirm('removeChecked()?')){
        return;
    }

    for(var key in window.localStorage){
        if(document.getElementById('checkbox-' + key).checked){
            window.localStorage.removeItem(key);
        }
    }

    refresh();
}

function removeItem(key, element){
    if(!window.confirm('Remove ' + key + '?')){
        return;
    }

    window.localStorage.removeItem(key);
    element.parentElement.parentElement.remove();
}

window.onload = function(e){
    init_input(
      {
        82: {
          'todo': refresh,
        },
      }
    );
    refresh();
};
<commit_msg>switched to header class usage<commit_after>'use strict';

function checkAll(){
    var checkboxes = document.getElementsByTagName('input');
    for(var checkbox in checkboxes){
        if(checkboxes[checkbox].type === 'checkbox'){
            checkboxes[checkbox].checked = true;
        }
    }
}

function check_checkbox(id){
    if(!document.body.contains(document.getElementById(id))){
        return;
    }

    document.getElementById('checkbox-' + id).checked =
      !document.getElementById('checkbox-' + id).checked;
}

function clearAll(){
    if(!window.confirm('window.localStorage.clear()?')){
        return;
    }

    window.localStorage.clear();
    refresh();
}

function headers(){
    document.getElementById('result').innerHTML =
      '
'
        + 'Key <input onclick=refresh() type=button value="Refresh [R]">'
          + '<input onclick=checkAll() type=button value=checkAll()>'
          + '<input onclick=removeChecked() type=button value=removeChecked()>'
        + '<input onclick=removeAll() type=button value=removeAll()>'
          + '<input onclick=clearAll() type=button value=window.localStorage.clear()>'
          + ' Value'
      + document.getElementById('result').innerHTML;
}

function refresh(){
    var output = '';

    for(var key in window.localStorage){
        output += '
<input disabled id="checkbox-'
          + key
          + '" type=checkbox><input onclick="removeItem(\'' + key + '\', this)" type=button value=X> '
          + key
          + ''
          + window.localStorage.getItem(key);
    }

    document.getElementById('result').innerHTML = output;

    headers();
}

function removeAll(){
    if(!window.confirm('removeAll()?')){
        return;
    }

    for(var key in window.localStorage){
        window.localStorage.removeItem(key);
    }

    document.getElementById('result').innerHTML = '';
    headers();
}

function removeChecked(){
    if(!window.confirm('removeChecked()?')){
        return;
    }

    for(var key in window.localStorage){
        if(document.getElementById('checkbox-' + key).checked){
            window.localStorage.removeItem(key);
        }
    }

    refresh();
}

function removeItem(key, element){
    if(!window.confirm('Remove ' + key + '?')){
        return;
    }

    window.localStorage.removeItem(key);
    element.parentElement.parentElement.remove();
}

window.onload = function(e){
    init_input(
      {
        82: {
          'todo': refresh,
        },
      }
    );
    refresh();
};
<|endoftext|>