<commit_before>define([
    'js-utils/js/base-page',
    'settings/js/validate-on-save-modal',
    'js-utils/js/confirm',
    'js-utils/js/ensure-array',
    'js-utils/js/listenable',
    'text!settings/templates/settings-page.html'
], function(BasePage, SaveModal, confirm, ensureArray, listenable, template) {

    return BasePage.extend({
        // === Override these === //
        configModel: {},
        icon: 'icon-cog',
        initializeWidgets: $.noop,
        router: {},
        routeEvent: 'route:settings',
        routeRoot: '',
        scrollSelector: {},
        strings: {},
        validateUrl: '',
        vent: {},
        leftWidgets: [],
        rightWidgets: [],
        middleWidgets: [],
        // ====================== //

        events: {
            'click .settings-restore': 'handleCancelButton',
            'submit form': 'handleSubmit'
        },

        initialize: function() {
            BasePage.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'getConfig', 'handleBeforeUnload', 'initializeWidgets', 'loadFromConfig', 'scrollToWidget', 'validate');
            this.listenTo(listenable(window), 'beforeunload', this.handleBeforeUnload);
            this.initializeWidgets();
            this.widgets = this.leftWidgets.concat(this.middleWidgets.concat(this.rightWidgets));
            this.isValidated = false;

            _.each(this.widgets, function(widget) {
                widget.on('validate', function() {
                    this.validate(widget);
                }, this);
            }, this);

            this.router.on(this.routeEvent, this.handleRouting, this);
        },

        render: function() {
            this.$el.html(_.template(template, {icon: this.icon, strings: this.strings}));
            this.$form = this.$('form');
            this.$scrollElement = $(this.scrollSelector);

            _.invoke(this.widgets, 'render');

            _.each(this.leftWidgets, function(widget) {
                this.$form.find('.left-widgets').append(widget.el)
            }, this);

            _.each(this.middleWidgets, function(widget) {
                this.$form.find('.middle-widgets').append(widget.el)
            }, this);

            _.each(this.rightWidgets, function(widget) {
                this.$form.find('.right-widgets').append(widget.el)
            }, this);

            this.configModel.onLoad(this.loadFromConfig);
            this.configModel.loaded || this.configModel.fetch();
            this.hasRendered = true;
        },

        getConfig: function() {
            var config = {};

            _.each(this.widgets, function(widget) {
                config[widget.configItem] = widget.getConfig();
            });

            return config;
        },

        handleBeforeUnload: function() {
            if (this.lastSavedConfig && !_.isEqual(this.getConfig(), this.lastSavedConfig)) {
                setTimeout(_.bind(function() {
                    this.vent.navigate(this.routeRoot, {trigger: true});
                }, this), 100);

                return this.strings.confirmUnload;
            }
        },

        handleCancelButton: function() {
            confirm({
                cancelClass: '',
                cancelIcon: 'icon-remove',
                cancelText: this.strings.cancelCancel,
                okText: this.strings.cancelOk,
                okClass: 'btn-warning',
                okIcon: 'icon-undo',
                message: this.strings.cancelMessage,
                title: this.strings.cancelTitle,
                okHandler: _.bind(function() {
                    this.loadFromConfig();
                    this.$scrollElement.scrollTop(0);
                }, this)
            });

            return false;
        },

        handleRouting: function(configItem) {
            if (configItem) {
                this.hasRendered || this.render();

                var widget = _.find(this.widgets, function(widget) {
                    return widget.configItem === configItem;
                });

                widget && this.scrollToWidget(widget);
                this.vent.navigate(this.routeRoot, {replace: true, trigger: false});
            }
        },

        handleSubmit: function() {
            var currentConfig = this.getConfig();
            var passedClientValidation = true;
            var hasScrolled = false;

            _.each(this.widgets, function(widget) {
                var isValid = widget.validateInputs();

                if (!isValid) {
                    passedClientValidation = false;
                    if (!hasScrolled) {
                        this.scrollToWidget(widget);
                        hasScrolled = true;
                    }
                }
            }, this);

            if (passedClientValidation) {
                var serversToValidate = [];

                _.each(this.widgets, function(widget) {
                    if (widget.shouldValidate()) {
                        serversToValidate.push(widget.configItem);
                        widget.lastValidationConfig = currentConfig[widget.configItem];
                    }
                });

                var saveModal = new SaveModal({
                    config: currentConfig,
                    configModel: this.configModel,
                    success: _.bind(function() {
                        this.lastSavedConfig = currentConfig;
                        this.$scrollElement.scrollTop(0);
                    }, this),
                    strings: this.strings.saveModal
                });

                // ensures validation on save is propagated to associated widget
                saveModal.on('validation', function(validation) {
                    if (validation === 'SUCCESS') {
                        validation = {};

                        _.each(serversToValidate, function(serverName) {
                            validation[serverName] = true;
                        });
                    }

                    _.each(validation, function(isValid, serverName) {
                        var widget = _.find(this.widgets, function(widget) {
                            return widget.configItem === serverName;
                        });

                        widget.handleValidation(currentConfig[serverName], {valid: isValid});
                    }, this);

                    this.isValidated = true;
                }, this);
            }

            return false;
        },

        loadFromConfig: function() {
            var config = this.configModel.get('config');
            var serversToValidate = [];
            this.$('.settings-description').text(this.strings.description(this.configModel.get('configEnvVariable'), this.configModel.get('configPath')));

            _.each(this.widgets, function(widget) {
                widget.updateConfig(config[widget.configItem]);

                if (widget.shouldValidate()) {
                    serversToValidate.push(widget);
                }
            });

            this.validate(serversToValidate);
            this.lastSavedConfig = this.getConfig();
        },

        scrollToWidget: function(widget) {
            widget.$('.collapse-' + widget.configItem).collapse('show').on('shown', _.bind(function() {
                this.$scrollElement.scrollTop(this.$scrollElement.scrollTop() + widget.$el.position().top - this.$scrollElement.offset().top);
            }, this));
        },

        validate: function(servers) {
            servers = ensureArray(servers);
            var config = {};
            var isValidated = this.isValidated;

            _.each(servers, function(server) {
                server.lastValidationConfig = config[server.configItem] = server.getConfig();
            });

            $.ajax(this.validateUrl, {
                contentType: 'application/json',
                dataType: 'json',
                type: 'POST',
                data: JSON.stringify(config),
                success: function(response) {
                    _.each(servers, function(server) {
                        server.handleValidation(config[server.configItem], response[server.configItem]);
                    });
                }
            });
            return isValidated;
        }
    });
});
<commit_msg>[settings-page] Merging settings-page-1.2 into trunk [rev:matthew.gordon2]<commit_after>define([
    'js-utils/js/base-page',
    'settings/js/validate-on-save-modal',
    'js-utils/js/confirm',
    'js-utils/js/ensure-array',
    'js-utils/js/listenable',
    'text!settings/templates/settings-page.html'
], function(BasePage, SaveModal, confirm, ensureArray, listenable, template) {

    return BasePage.extend({
        // === Override these === //
        configModel: {},
        icon: 'icon-cog',
        initializeWidgets: $.noop,
        router: {},
        routeEvent: 'route:settings',
        routeRoot: '',
        scrollSelector: {},
        strings: {},
        validateUrl: '',
        vent: {},
        leftWidgets: [],
        rightWidgets: [],
        middleWidgets: [],
        // ====================== //

        events: {
            'click .settings-restore': 'handleCancelButton',
            'submit form': 'handleSubmit'
        },

        initialize: function() {
            BasePage.prototype.initialize.apply(this, arguments);

            _.bindAll(this, 'getConfig', 'handleBeforeUnload', 'initializeWidgets', 'loadFromConfig', 'scrollToWidget', 'validate');
            this.listenTo(listenable(window), 'beforeunload', this.handleBeforeUnload);
            this.initializeWidgets();
            this.widgets = this.leftWidgets.concat(this.middleWidgets.concat(this.rightWidgets));
            this.isValidated = false;

            _.each(this.widgets, function(widget) {
                widget.on('validate', function() {
                    this.validate(widget);
                }, this);
            }, this);

            this.router.on(this.routeEvent, this.handleRouting, this);
        },

        render: function() {
            this.$el.html(_.template(template, {icon: this.icon, strings: this.strings}));
            this.$form = this.$('form');
            this.$scrollElement = $(this.scrollSelector);

            _.invoke(this.widgets, 'render');

            _.each(this.leftWidgets, function(widget) {
                this.$form.find('.left-widgets').append(widget.el)
            }, this);

            _.each(this.middleWidgets, function(widget) {
                this.$form.find('.middle-widgets').append(widget.el)
            }, this);

            _.each(this.rightWidgets, function(widget) {
                this.$form.find('.right-widgets').append(widget.el)
            }, this);

            this.configModel.onLoad(this.loadFromConfig);
            this.configModel.loaded || this.configModel.fetch();
            this.hasRendered = true;
        },

        getConfig: function() {
            var config = {};

            _.each(this.widgets, function(widget) {
                config[widget.configItem] = widget.getConfig();
            });

            return config;
        },

        canLeavePage: function() {
            return _.isEqual(this.getConfig(), this.lastSavedConfig);
        },

        handleBeforeUnload: function() {
            if (!this.canLeavePage()) {
                setTimeout(_.bind(function() {
                    this.vent.navigate(this.routeRoot, {trigger: true});
                }, this), 100);

                return this.strings.confirmUnload;
            }
        },

        handleCancelButton: function() {
            confirm({
                cancelClass: '',
                cancelIcon: 'icon-remove',
                cancelText: this.strings.cancelCancel,
                okText: this.strings.cancelOk,
                okClass: 'btn-warning',
                okIcon: 'icon-undo',
                message: this.strings.cancelMessage,
                title: this.strings.cancelTitle,
                okHandler: _.bind(function() {
                    this.loadFromConfig();
                    this.$scrollElement.scrollTop(0);
                }, this)
            });

            return false;
        },

        handleRouting: function(configItem) {
            if (configItem) {
                this.hasRendered || this.render();

                var widget = _.find(this.widgets, function(widget) {
                    return widget.configItem === configItem;
                });

                widget && this.scrollToWidget(widget);
                this.vent.navigate(this.routeRoot, {replace: true, trigger: false});
            }
        },

        handleSubmit: function() {
            var currentConfig = this.getConfig();
            var passedClientValidation = true;
            var hasScrolled = false;

            _.each(this.widgets, function(widget) {
                var isValid = widget.validateInputs();

                if (!isValid) {
                    passedClientValidation = false;
                    if (!hasScrolled) {
                        this.scrollToWidget(widget);
                        hasScrolled = true;
                    }
                }
            }, this);

            if (passedClientValidation) {
                var serversToValidate = [];

                _.each(this.widgets, function(widget) {
                    if (widget.shouldValidate()) {
                        serversToValidate.push(widget.configItem);
                        widget.lastValidationConfig = currentConfig[widget.configItem];
                    }
                });

                var saveModal = new SaveModal({
                    config: currentConfig,
                    configModel: this.configModel,
                    success: _.bind(function() {
                        this.lastSavedConfig = currentConfig;
                        this.$scrollElement.scrollTop(0);
                    }, this),
                    strings: this.strings.saveModal
                });

                // ensures validation on save is propagated to associated widget
                saveModal.on('validation', function(validation) {
                    if (validation === 'SUCCESS') {
                        validation = {};

                        _.each(serversToValidate, function(serverName) {
                            validation[serverName] = true;
                        });
                    }

                    _.each(validation, function(isValid, serverName) {
                        var widget = _.find(this.widgets, function(widget) {
                            return widget.configItem === serverName;
                        });

                        widget.handleValidation(currentConfig[serverName], {valid: isValid});
                    }, this);

                    this.isValidated = true;
                }, this);
            }

            return false;
        },

        loadFromConfig: function() {
            var config = this.configModel.get('config');
            var serversToValidate = [];
            this.$('.settings-description').text(this.strings.description(this.configModel.get('configEnvVariable'), this.configModel.get('configPath')));

            _.each(this.widgets, function(widget) {
                widget.updateConfig(config[widget.configItem]);

                if (widget.shouldValidate()) {
                    serversToValidate.push(widget);
                }
            });

            this.validate(serversToValidate);
            this.lastSavedConfig = this.getConfig();
        },

        scrollToWidget: function(widget) {
            widget.$('.collapse-' + widget.configItem).collapse('show').on('shown', _.bind(function() {
                this.$scrollElement.scrollTop(this.$scrollElement.scrollTop() + widget.$el.position().top - this.$scrollElement.offset().top);
            }, this));
        },

        validate: function(servers) {
            servers = ensureArray(servers);
            var config = {};
            var isValidated = this.isValidated;

            _.each(servers, function(server) {
                server.lastValidationConfig = config[server.configItem] = server.getConfig();
            });

            $.ajax(this.validateUrl, {
                contentType: 'application/json',
                dataType: 'json',
                type: 'POST',
                data: JSON.stringify(config),
                success: function(response) {
                    _.each(servers, function(server) {
                        server.handleValidation(config[server.configItem], response[server.configItem]);
                    });
                }
            });
            return isValidated;
        }
    });
});

{'fraction_non_alphanumeric': 0.12452403617325083, 'fraction_numerical': 0.0007734412184673964, 'mean_word_length': 1.4976225854383358, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2881336', 'n_tokens_mistral': 4186, 'n_tokens_neox': 3946, 'n_words': 642}