<commit_before>/*
 Copyright (C) 2015-2017 GEM Foundation

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var htmlEscape = function (record) {
        // record[3] is the log message
        record[3] = record[3].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
        return record
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                              $('h1', pleaseWaitDiv).text(msg);
                              if (progress) {
                                  progressHandlingFunction({loaded: 0, total: 1});
                              } else {
                                  progressHandlingFunction({loaded: 1, total: 1});
                              }
                              pleaseWaitDiv.modal('show');
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(is_large, title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              if (is_large) {
                                  errorDiv.addClass("errorDialogLarge");
                              }
                              else {
                                  errorDiv.removeClass("errorDialogLarge");
                              }
                              errorDiv.modal('show');
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-show-remove": "remove_calculation",
                "click .btn-danger": "show_modal_confirm_remove_calculation",
                "click .btn-hide-remove": "hide_modal_confirm_remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            show_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var show_or_back = (function(e) {
                    this.conf_show = $('#confirmDialog' + calc_id).show();
                    this.back_conf_show = $('.back_confirmDialog' + calc_id).show();
                    closeTimer();
                })();
            },

            hide_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var calc_desc = $(e.target).attr('data-calc-desc');
                var view = this;
                diaerror.show(false, "Removing calculation " + calc_id, "...");
                $.post(gem_oq_server_url + "/v1/calc/" + calc_id + "/remove"
                     ).success(
                         function(data, textStatus, jqXHR)
                         {
                             diaerror.show(false, "Removed calculation ", "Calculation:
(" + calc_id + ") " + calc_desc + " removed.");
                             view.calculations.remove([view.calculations.get(calc_id)]);
                         }
                     ).error(
                         function(jqXHR, textStatus, errorThrown)
                         {
                             if (jqXHR.status == 404) {
                                 diaerror.show(false, "Removing calculation ", "Failed calculation:
(" + calc_id + ") " + calc_desc + " not found.");
                             }
                             else {
                                 diaerror.show(false, "Removing calculation " + calc_id, "Failed: " + textStatus);
                             }
                         }
                     );
                 setTimer();
            },

            show_traceback: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show(false, "Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show(false, "Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show(true, "Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show(true, "Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show(true, "Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show(true, "Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + htmlEscape(data[s]) + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(true, title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" ) || was_running == false) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                if (is_running)
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).show();
                else
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).hide();

                this._show_log_priv(true, calc_id, is_running, "0");
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    function closeTimer() {
        refresh_calcs = clearInterval(refresh_calcs);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show(false, "Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<commit_msg>Changed Title Removed calculation in Calculation removed<commit_after>/*
 Copyright (C) 2015-2017 GEM Foundation

 This program is free software: you can redistribute it and/or modify
 it under the terms of the GNU Affero General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful,
 but WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 GNU Affero General Public License for more details.

 You should have received a copy of the GNU Affero General Public License
 along with this program.  If not, see <https://www.gnu.org/licenses/agpl.html>.
 */

(function($, Backbone, _) {
    var calculation_table;

    var progressHandlingFunction = function(progress) {
        var percent = progress.loaded / progress.total * 100;
        $('.bar').css('width', percent + '%');
        if (percent == 100) {
            dialog.hide();
        }
    };

    var htmlEscape = function (record) {
        // record[3] is the log message
        record[3] = record[3].replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
        return record
    };

    var dialog = (function ()
                  {
                      var pleaseWaitDiv = $('

Processing...

');
                      return {
                          show: function(msg, progress) {
                              $('h1', pleaseWaitDiv).text(msg);
                              if (progress) {
                                  progressHandlingFunction({loaded: 0, total: 1});
                              } else {
                                  progressHandlingFunction({loaded: 1, total: 1});
                              }
                              pleaseWaitDiv.modal('show');
                          },
                          hide: function () {
                              pleaseWaitDiv.modal('hide');
                          }
                      };
                  })();

    var diaerror = (function ()
                  {
                      var errorDiv = $('
\

\

\

\

Calculation not accepted: traceback
\

\

\

\

\
                      <input type="checkbox" id="diaerror_scroll_enabled" checked>\
                      Auto Scroll   \
                      <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>\

\

\

\

');
                      errorDiv.bind('hide', function() { calculation_table.hide_log(); });
                      return {
                          getdiv: function() {
                              return errorDiv;
                          },

                          show: function(is_large, title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              if (msg != null) {
                                  $('.modal-body-pre', errorDiv).html(msg);
                              }
                              if (is_large) {
                                  errorDiv.addClass("errorDialogLarge");
                              }
                              else {
                                  errorDiv.removeClass("errorDialogLarge");
                              }
                              errorDiv.modal('show');
                          },

                          append: function(title, msg) {
                              if (title != null) {
                                  $('.modal-title', errorDiv).html(title);
                              }
                              $( msg ).appendTo( $('.modal-body-pre', errorDiv) );
                          },

                          scroll_to_bottom: function(ctx) {
                              ctx.scrollTop(ctx[0].scrollHeight);
                          },

                          hide: function () {
                              errorDiv.modal('hide');
                          }
                      };
                  })();

    var CalculationTable = Backbone.View.extend(
        {
            /* the html element where the table is rendered */
            el: $('#my-calculations'),

            logXhr: null,
            logId: -1,
            logIsNew: false,
            logLinesAll: 0,
            logLines: 0,
            logTimeout: null,

            initialize: function(options) {

                /* whatever happens to any calculation, re-render the table */
                _.bindAll(this, 'render');
                this.calculations = options.calculations;
                this.calculations.bind('reset', this.render);
                this.calculations.bind('add', this.render);
                this.calculations.bind('remove', this.render);

                /* if false, it prevents the table to be refreshed */
                this.can_be_rendered = true;

                this.render();
            },

            events: {
                "click .btn-show-remove": "remove_calculation",
                "click .btn-danger": "show_modal_confirm_remove_calculation",
                "click .btn-hide-remove": "hide_modal_confirm_remove_calculation",
                "click .btn-traceback": "show_traceback",
                "click .btn-log": "show_log",
                "click .btn-file": "on_run_risk_clicked",
                "change .btn-file input": "on_run_risk_queued"
            },

            /* When an input dialog is opened, it is very important to not re-render the table */
            on_run_risk_clicked: function(e) {
                /* if a file input dialog has been opened do not refresh the calc table */
                this.can_be_rendered = false;
            },

            on_run_risk_queued: function(e) {
                this.can_be_rendered = true;
            },

            show_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var show_or_back = (function(e) {
                    this.conf_show = $('#confirmDialog' + calc_id).show();
                    this.back_conf_show = $('.back_confirmDialog' + calc_id).show();
                    closeTimer();
                })();
            },

            hide_modal_confirm_remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');

                var hide_or_back = (function(e) {
                    this.conf_hide = $('#confirmDialog' + calc_id).hide();
                    this.back_conf_hide = $('.back_confirmDialog' + calc_id).hide();
                    setTimer();
                })();
            },

            remove_calculation: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var calc_desc = $(e.target).attr('data-calc-desc');
                var view = this;
                diaerror.show(false, "Removing calculation " + calc_id, "...");
                $.post(gem_oq_server_url + "/v1/calc/" + calc_id + "/remove"
                     ).success(
                         function(data, textStatus, jqXHR)
                         {
                             diaerror.show(false, "Calculation removed ", "Calculation:
(" + calc_id + ") " + calc_desc + " removed.");
                             view.calculations.remove([view.calculations.get(calc_id)]);
                         }
                     ).error(
                         function(jqXHR, textStatus, errorThrown)
                         {
                             if (jqXHR.status == 404) {
                                 diaerror.show(false, "Removing calculation ", "Failed calculation:
(" + calc_id + ") " + calc_desc + " not found.");
                             }
                             else {
                                 diaerror.show(false, "Removing calculation " + calc_id, "Failed: " + textStatus);
                             }
                         }
                     );
                 setTimer();
            },

            show_traceback: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var myXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/traceback",
                                    error: function (jqXHR, textStatus, errorThrown) {
                                        if (jqXHR.status == 404) {
                                            diaerror.show(false, "Calculation " + calc_id + " not found.");
                                        }
                                        else {
                                            diaerror.show(false, "Error retrieving traceback for calculation " + calc_id, textStatus);
                                        }
                                        // alert("Error: " + textStatus);
                                    },
                                    success: function (data, textStatus, jqXHR) {
                                        if (data.length == 0) {
                                            diaerror.show(true, "Traceback not found for calculation " + calc_id, []);
                                        }
                                        else {
                                            var out = "";
                                            var ct = 0;
                                            for (s in data) {
                                                if (data[s] == "")
                                                    continue;
                                                out += '
' + data[s] + '
';
                                                ct++;
                                            }
                                            diaerror.show(true, "Traceback of calculation " + calc_id, out);
                                        }
                                        // alert("Success: " + textStatus);
                                    }});
            },

            _show_log_priv: function(is_new, calc_id, is_running, from) {
                var was_running = is_running;

                // TO CHECK hide_log method enable console.log and take a look
                // console.log("_show_log_priv: begin");
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                if (is_new) {
                    if (this.logTimeout != null) {
                        window.clearTimeout(this.logTimeout);
                        this.logTimeout = null;
                    }
                }
                var obj = this;

                this.logXhr = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/log/" + from + ":",
                                      error: function (jqXHR, textStatus, errorThrown) {
                                          if (jqXHR.status == 404) {
                                              diaerror.show(true, "Log of calculation " + calc_id + " not found.");
                                          }
                                          else {
                                              diaerror.show(true, "Error retrieving log for calculation " + calc_id, textStatus);
                                          }
                                          obj.logIsNew = false;
                                      },
                                      success: function (data, textStatus, jqXHR) {
                                          var delay = 250;

                                          if (is_new) {
                                              obj.logLines = 0;
                                              obj.logLinesAll = 0;
                                          }
                                          else {
                                              // if data is empty check if job is still running
                                              if (is_running) {
                                                  if (data.length == 0) {
                                                      var ajax, status;

                                                      delay = 1000;

                                                      ajax = $.ajax({url: gem_oq_server_url + "/v1/calc/" + calc_id + "/status",
                                                                     async: false}).done(function (data) { status = data.is_running; });
                                                      if (status !== true) {
                                                          is_running = false;
                                                      }
                                                  }
                                              }
                                          }
                                          var out = "";

                                          for (s in data) {
                                              if (data[s] == "") {
                                                  obj.logLinesAll++;
                                                  continue;
                                              }
                                              out += '
' + htmlEscape(data[s]) + '
';
                                              obj.logLines++;
                                              obj.logLinesAll++;
                                          }

                                          var title;
                                          if (is_running) {
                                              var dt;
                                              dt = new Date();
                                              title = "Log of calculation " + calc_id + " - " + dt.toString();
                                          }
                                          else if (was_running != is_running) {
                                              title = "Log of calculation " + calc_id + " - finished";
                                          }
                                          else {
                                              title = "Log of calculation " + calc_id;
                                          }

                                          if (obj.logIsNew) {
                                              diaerror.show(true, title, out);
                                          }
                                          else {
                                              diaerror.append(title, out);
                                          }
                                          if ($("#diaerror_scroll_enabled").prop( "checked" ) || was_running == false) {
                                              diaerror.scroll_to_bottom($('.modal-body', diaerror.getdiv()));
                                          }

                                          if (is_running) {
                                              function log_update(obj)
                                              {
                                                  obj._show_log_priv(false, obj.logId, true, obj.logLinesAll);
                                              }

                                              obj.logTimeout = window.setTimeout(log_update, delay, obj);
                                          }
                                          else {
                                              $('#diaerror_scroll_enabled_box').hide();
                                          }

                                          obj.logIsNew = false;
                                      }});
            },

            show_log: function(e) {
                e.preventDefault();
                var calc_id = $(e.target).attr('data-calc-id');
                var is_running = ($(e.target).attr('is-running') == "true");

                this.logId = calc_id;
                this.logIsNew = true;

                if (is_running)
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).show();
                else
                    $('#diaerror_scroll_enabled_box', diaerror.getdiv()).hide();

                this._show_log_priv(true, calc_id, is_running, "0");
            },

            hide_log: function(e) {
                if (this.logTimeout != null) {
                    window.clearTimeout(this.logTimeout);
                    this.logTimeout = null;
                }
                if (this.logXhr != null) {
                    this.logXhr.abort();
                    this.logXhr = null;
                }
                $('#diaerror_scroll_enabled_box').hide();
            },

            render: function() {
                if (!this.can_be_rendered) {
                    return;
                };
                this.$el.html(_.template($('#calculation-table-template').html(),
                                         { calculations: this.calculations.models }));
            }
        });

    var Calculation = Backbone.Model.extend({
        defaults: {
            map: null,
            layers: []
        }
    });

    var Calculations = Backbone.Collection.extend(
        {
            model: Calculation,
            url: gem_oq_server_url + "/v1/calc/list?relevant=true"
        });
    var calculations = new Calculations();

    var refresh_calcs;

    function setTimer() {
        refresh_calcs = setInterval(function() { calculations.fetch({reset: true}) }, 3000);
    }

    function closeTimer() {
        refresh_calcs = clearInterval(refresh_calcs);
    }

    /* classic event management */
    $(document).ready(
        function() {
            calculation_table = new CalculationTable({ calculations: calculations });
            calculations.fetch({reset: true});
            setTimer();

            /* XXX. Reset the input file value to ensure the change event
               will be always triggered */
            $(document).on("click", 'input[name=archive]',
                           function(e) { this.value = null; });
            $(document).on("change", 'input[name=archive]',
                           function(e) {
                               dialog.show('Uploading calculation', true);
                               var input = $(e.target);
                               var form = input.parents('form')[0];

                               $(form).ajaxSubmit(
                                   {
                                    xhr: function() {  // custom xhr to add progress bar management
                                        var myXhr = $.ajaxSettings.xhr();
                                        if(myXhr.upload){ // if upload property exists
                                            myXhr.upload.addEventListener('progress', progressHandlingFunction, false);
                                        }
                                        return myXhr;
                                    },
                                    success: function(data) {
                                        calculations.add(new Calculation(data), {at: 0});
                                    },
                                    error: function(xhr) {
                                        dialog.hide();
                                        var s, out, data = $.parseJSON(xhr.responseText);
                                        var out = "";
                                        var ct = 0;
                                        for (s in data) {
                                            if (data[s] == "")
                                                continue;
                                            out += '
' + data[s] + '
';
                                            ct++;
                                        }
                                        diaerror.show(false, "Calculation not accepted: traceback", out);
                                    }});
                           });

            $(document).on('hidden.bs.modal', 'div[id^=traceback-]',
                           function(e) {
                               setTimer();
                           });

        });
})($, Backbone, _, gem_oq_server_url);
<|endoftext|>


<commit_before>'use strict'

var _ = require('lodash')
var Promise = require('bluebird')
var moment = require('moment-timezone')

module.exports = {

  getEdit: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data)
        .then(function (data) {
          return api.models[ modelName ].findOne({ where: { id: data.params.id } })
        })
        .then(function (record) {
          if (!record) {
            data.connection.rawConnection.responseHttpCode = 404
            return Promise.reject(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
          }

          if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
            data.connection.rawConnection.responseHttpCode = 401
            return Promise.reject(new Error(api.config.errors.sessionNoPermission(data.connection)))
          }

          return record
        })
        .then(function (record) {
          if ((!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName)) || !data.params.user) {
            data.params.user = data.session.userId
          }
          console.log(data.params.user)
          return record
        })
        .then(function (record) {
          return record.apiUpdate(data.params)
        })
        .then(function (record) {
          return record.save()
        })
        .then(function (record) {
          return record.apiData(api)
        })
        .then(function (res) {
          data.response.data = res
          return res
        })
        .then(function () {
          next()
        })
        .catch(function (error) {
          api.log(error, 'error')
          next(error)
        })
    }
  },

  getInsert: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data)
        .then(function (data) {
          return api.models[ modelName ].build({})
        })
        .then(function (record) {
          if ((!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName)) || !data.params.user) {
            data.params.user = data.session.userId
          }
          return record
        })
        .then(function (record) {
          return record.apiUpdate(data.params)
        })
        .then(function (record) {
          var hash = record.calculateHash()
          api.log('looking for %s with hash %s', 'info', modelName, hash)
          return api.models[ modelName ].findOne({ where: { hash: hash } })
            .then(function (existing) {
              if (existing) {
                api.log('found %s with hash %s, updating', 'info', modelName, hash)
                data.response.existing = true
                return Promise.resolve()
                  .then(function () {
                    return existing.apiUpdate(data.params)
                  })
                  .then(function () {
                    return existing.save()
                  })
              } else {
                api.log('not found %s with hash %s, creating', 'info', modelName, hash)
                return record.save()
              }
            })
        })
        .then(function (record) {
          return record.apiData(api)
        })
        .then(function (res) {
          data.response.data = res
          return res
        })
        .then(function () {
          next()
        })
        .catch(function (error) {
          api.logger.error(error)
          next(error)
        })
    }
  },

  getView: function (modelName) {
    return function (api, data, next) {
      var q = {
        where: { id: data.params.id }
      }

      api.models[ modelName ]
        .findOne(q)
        .then(function (record) {
          if (!record) {
            data.connection.rawConnection.responseHttpCode = 404
            return next(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
          }

          if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
            data.connection.rawConnection.responseHttpCode = 401
            return next(new Error(api.config.errors.sessionNoPermission(data.connection)))
          }

          record.apiData(api).then(function (apiData) {
            data.response.data = apiData
            next()
          })
        })
        .catch(next)
    }
  },

  getDelete: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data).then(function (data) {
        return api.models[ modelName ].findOne({ where: { id: data.params.id } })
      }).then(function (record) {
        if (!record) {
          data.connection.rawConnection.responseHttpCode = 404
          return Promise.reject(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
        }

        if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
          data.connection.rawConnection.responseHttpCode = 401
          return Promise.reject(new Error(api.config.errors.sessionNoPermission(data.connection)))
        }

        return record
      }).then(function (record) {
        return record.destroy()
      }).then(function () {
        next()
      }).catch(function (error) {
        api.log('Exception', 'error', error)
        next(error)
      })
    }
  },

  defaultPrepareCsvSkipFields: [
    'observers',
    'hash',
    'user',
    'speciesInfo',
    'observationDateTime',
    'endDateTime',
    'startDateTime',
    'imported',
    'createdAt',
    'updatedAt'
  ],

  defaultPrepareCsv: function (api, record) {
    return _.omitBy(record, function (value, key) {
      return module.exports.defaultPrepareCsvSkipFields.indexOf(key.split('.')[ 0 ]) !== -1
    })
  },

  getSelect: function (modelName, prepareQuery, prepareCsv) {
    if (!prepareCsv) {
      prepareCsv = module.exports.defaultPrepareCsv
    }
    return function (api, data, next) {
      try {
        return Promise.resolve(prepareQuery(api, data))
          .then(function (q) {
            if (data.session.user.isAdmin || api.forms.isModerator(data.session.user, modelName)) {
              if (data.params.user) {
                q.where = _.extend(q.where || {}, {
                  userId: data.params.user
                })
              }
            } else {
              q.where = _.extend(q.where || {}, {
                userId: data.session.userId
              })
            }
            return q
          })
          .then(function (q) {
            switch (data.connection.extension) {
              case 'zip':
              case 'csv':
                q.include = (q.include || []).concat([
                  { model: api.models.user, as: 'user' }
                ])

                if (api.models[ modelName ].associations.speciesInfo) {
                  q.include = q.include.concat([
                    { model: api.models.species, as: 'speciesInfo' }
                  ])
                }

                q.raw = true
                break
            }
            return q
          })
          .then(function (q) {
            return api.models[ modelName ].findAndCountAll(q)
          })
          .then(function (result) {
            switch (data.connection.extension) {
              case 'zip':
              case 'csv':
                return new Promise(function (resolve, reject) {
                  var i, l, record
                  for (i = 0, l = result.rows.length; i < l; ++i) {
                    record = result.rows[ i ]
                    var pre = {
                      startTime: moment.tz(record.startDateTime, api.config.formats.tz).format(api.config.formats.time),
                      startDate: moment.tz(record.startDateTime, api.config.formats.tz).format(api.config.formats.date),
                      endTime: moment.tz(record.endDateTime, api.config.formats.tz).format(api.config.formats.time),
                      endDate: moment.tz(record.endDateTime, api.config.formats.tz).format(api.config.formats.date),
                      email: record[ 'user.email' ],
                      firstName: record[ 'user.firstName' ],
                      lastName: record[ 'user.lastName' ],
                      observationDate: moment.tz(record.observationDateTime, api.config.formats.tz).format(api.config.formats.date),
                      observationTime: moment.tz(record.observationDateTime, api.config.formats.tz).format(api.config.formats.time),
                      observationDateUTC: moment.tz(record.observationDateTime, 'UTC').format(api.config.formats.date),
                      observationTimeUTC: moment.tz(record.observationDateTime, 'UTC').format(api.config.formats.time),
                      otherObservers: record.observers
                    }
                    var mid = prepareCsv(api, record)
                    var post = {
                      notes: (record.notes || '').replace(/[\n\r]+/g, ' '),
                      speciesNotes: (record.speciesNotes || '').replace(/[\n\r]+/g, ' '),
                      pictures: (record.pictures ? JSON.parse(record.pictures) || [] : []).map(function (pic) {
                        return pic.url && pic.url.split('/').slice(-1)[ 0 ] + '.jpg'
                      }).filter(function (val) {
                        return val
                      }).join(', ') || '',
                      track: record.track && record.monitoringCode + '.gpx',
                      trackId: record.track && record.track.split('/').slice(-1)[ 0 ]
                    }

                    if (api.models[modelName].associations.speciesInfo) {
                      post.species = record['speciesInfo.labelLa'] + ' | ' + record['speciesInfo.labelBg']
                      post.speciesEn = record['speciesInfo.labelLa'] + ' | ' + record['speciesInfo.labelEn']
                    }

                    result.rows[ i ] = _.assign(pre, mid, post)
                  }
                  require('csv-stringify')(result.rows, {
                    delimiter: ';',
                    header: true
                  }, function (err, csv) {
                    if (err) {
                      return reject(err)
                    }
                    switch (data.connection.extension) {
                      case 'zip':
                        return resolve({
                          csv: csv,
                          data: result.rows
                        })
                      case 'csv':
                      default:
                        return resolve(csv)
                    }
                  })
                })
              default:
                return Promise
                  .map(result.rows, function (model) {
                    return model.apiData(api)
                  })
                  .then(function (data) {
                    return {
                      count: result.count,
                      data: data
                    }
                  })
            }
          })
          .then(function (response) {
            switch (data.connection.extension) {
              case 'zip':
                return new Promise(function (resolve, reject) {
                  var archiver = require('archiver')
                  var archive = archiver.create('zip', {})
                  archive.on('error', function (err) {
                    api.log('archive error: %s', 'error', err)
                    reject(err)
                  })

                  var fs = require('fs')
                  var uuid = require('uuid')
                  var path = require('path')
                  var outputFilename = path.join(api.config.general.paths.fileupload[ 0 ], uuid.v4() + '.zip')
                  var output = fs.createWriteStream(outputFilename)
                  output.on('error', function (err) {
                    api.log('output error: %s', 'error', err)
                    reject(err)
                  })
                  output.on('finish', function () {
                    api.log('tempfile %s', 'info', outputFilename)
                    resolve(outputFilename)
                  })
                  archive.pipe(output)

                  archive.append(response.csv, { name: modelName + '.csv' })
                  var fileMap = {}

                  function appendFile (filename, id) {
                    if (!filename || filename in fileMap || id in fileMap) return
                    var idx = filename.lastIndexOf('.')
                    if (idx === -1) idx = filename.length
                    id = id || filename.substring(0, idx)
                    fileMap[ filename ] = id
                    fileMap[ id ] = filename
                    api.log('adding %s as %s', 'debug', id, filename)

                    return new Promise(function (resolve, reject) {
                      api.filestorage.get(id, function (err, stream) {
                        if (err) {
                          api.log('storage error:', 'error', err)
                          return reject(err)
                        }

                        resolve(archive.append(stream, { name: filename }))
                      })
                    })
                  }

                  Promise
                    .map(response.data, function (record) {
                      return Promise.all([
                        Promise.map(record.pictures ? record.pictures.split(', ') : [], function (picture) {
                          return Promise.resolve(appendFile(picture)).catch(function () {
                          })
                        }),
                        record.trackId && appendFile(record.track, record.trackId)
                      ])
                    })
                    .then(function () {
                      archive.finalize()
                    }, reject)
                })
              default:
                return response
            }
          })
          .then(function (response) {
            switch (data.connection.extension) {
              case 'csv':
                data.connection.rawConnection.responseHeaders.push([ 'Content-Type', 'text/csv' ])
                data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', 'attachment; filename="' + modelName + '.csv"' ])
                data.connection.sendMessage(response)
                data.toRender = false
                break
              case 'zip':
                data.connection.rawConnection.responseHeaders.push([ 'Content-Type', 'application/zip' ])
                data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', 'attachment; filename="' + modelName + '.zip"' ])
                var fs = require('fs')
                var stats = fs.statSync(response)
                var fileSize = stats[ 'size' ]
                api.servers.servers.web.sendFile(data.connection, null, fs.createReadStream(response), 'application/zip', fileSize)
                data.toRender = false
                break
              default:
                data.response = response
                return response
            }
          }).then(function () {
            next()
          })
          .catch(function (e) {
            api.log('Failure to retrieve records', 'error', e)
            next(e)
          })
      } catch (e) {
        api.log('Exception', 'error', e)
        next(e)
      }
    }
  }
}
<commit_msg>Use defined association when querying<commit_after>'use strict'

var _ = require('lodash')
var Promise = require('bluebird')
var moment = require('moment-timezone')

module.exports = {

  getEdit: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data)
        .then(function (data) {
          return api.models[ modelName ].findOne({ where: { id: data.params.id } })
        })
        .then(function (record) {
          if (!record) {
            data.connection.rawConnection.responseHttpCode = 404
            return Promise.reject(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
          }

          if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
            data.connection.rawConnection.responseHttpCode = 401
            return Promise.reject(new Error(api.config.errors.sessionNoPermission(data.connection)))
          }

          return record
        })
        .then(function (record) {
          if ((!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName)) || !data.params.user) {
            data.params.user = data.session.userId
          }
          console.log(data.params.user)
          return record
        })
        .then(function (record) {
          return record.apiUpdate(data.params)
        })
        .then(function (record) {
          return record.save()
        })
        .then(function (record) {
          return record.apiData(api)
        })
        .then(function (res) {
          data.response.data = res
          return res
        })
        .then(function () {
          next()
        })
        .catch(function (error) {
          api.log(error, 'error')
          next(error)
        })
    }
  },

  getInsert: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data)
        .then(function (data) {
          return api.models[ modelName ].build({})
        })
        .then(function (record) {
          if ((!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName)) || !data.params.user) {
            data.params.user = data.session.userId
          }
          return record
        })
        .then(function (record) {
          return record.apiUpdate(data.params)
        })
        .then(function (record) {
          var hash = record.calculateHash()
          api.log('looking for %s with hash %s', 'info', modelName, hash)
          return api.models[ modelName ].findOne({ where: { hash: hash } })
            .then(function (existing) {
              if (existing) {
                api.log('found %s with hash %s, updating', 'info', modelName, hash)
                data.response.existing = true
                return Promise.resolve()
                  .then(function () {
                    return existing.apiUpdate(data.params)
                  })
                  .then(function () {
                    return existing.save()
                  })
              } else {
                api.log('not found %s with hash %s, creating', 'info', modelName, hash)
                return record.save()
              }
            })
        })
        .then(function (record) {
          return record.apiData(api)
        })
        .then(function (res) {
          data.response.data = res
          return res
        })
        .then(function () {
          next()
        })
        .catch(function (error) {
          api.logger.error(error)
          next(error)
        })
    }
  },

  getView: function (modelName) {
    return function (api, data, next) {
      var q = {
        where: { id: data.params.id }
      }

      api.models[ modelName ]
        .findOne(q)
        .then(function (record) {
          if (!record) {
            data.connection.rawConnection.responseHttpCode = 404
            return next(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
          }

          if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
            data.connection.rawConnection.responseHttpCode = 401
            return next(new Error(api.config.errors.sessionNoPermission(data.connection)))
          }

          record.apiData(api).then(function (apiData) {
            data.response.data = apiData
            next()
          })
        })
        .catch(next)
    }
  },

  getDelete: function (modelName) {
    return function (api, data, next) {
      Promise.resolve(data).then(function (data) {
        return api.models[ modelName ].findOne({ where: { id: data.params.id } })
      }).then(function (record) {
        if (!record) {
          data.connection.rawConnection.responseHttpCode = 404
          return Promise.reject(new Error(api.config.errors.formNotFound(data.connection, modelName, data.params.id)))
        }

        if (!data.session.user.isAdmin && !api.forms.isModerator(data.session.user, modelName) && record.userId !== data.session.userId) {
          data.connection.rawConnection.responseHttpCode = 401
          return Promise.reject(new Error(api.config.errors.sessionNoPermission(data.connection)))
        }

        return record
      }).then(function (record) {
        return record.destroy()
      }).then(function () {
        next()
      }).catch(function (error) {
        api.log('Exception', 'error', error)
        next(error)
      })
    }
  },

  defaultPrepareCsvSkipFields: [
    'observers',
    'hash',
    'user',
    'speciesInfo',
    'observationDateTime',
    'endDateTime',
    'startDateTime',
    'imported',
    'createdAt',
    'updatedAt'
  ],

  defaultPrepareCsv: function (api, record) {
    return _.omitBy(record, function (value, key) {
      return module.exports.defaultPrepareCsvSkipFields.indexOf(key.split('.')[ 0 ]) !== -1
    })
  },

  getSelect: function (modelName, prepareQuery, prepareCsv) {
    if (!prepareCsv) {
      prepareCsv = module.exports.defaultPrepareCsv
    }
    return function (api, data, next) {
      try {
        return Promise.resolve(prepareQuery(api, data))
          .then(function (q) {
            if (data.session.user.isAdmin || api.forms.isModerator(data.session.user, modelName)) {
              if (data.params.user) {
                q.where = _.extend(q.where || {}, {
                  userId: data.params.user
                })
              }
            } else {
              q.where = _.extend(q.where || {}, {
                userId: data.session.userId
              })
            }
            return q
          })
          .then(function (q) {
            switch (data.connection.extension) {
              case 'zip':
              case 'csv':
                q.include = (q.include || []).concat([
                  api.models[ modelName ].associations.user
                ])

                if (api.models[ modelName ].associations.speciesInfo) {
                  q.include = q.include.concat([
                    api.models[ modelName ].associations.speciesInfo
                  ])
                }

                q.raw = true
                break
            }
            return q
          })
          .then(function (q) {
            return api.models[ modelName ].findAndCountAll(q)
          })
          .then(function (result) {
            switch (data.connection.extension) {
              case 'zip':
              case 'csv':
                return new Promise(function (resolve, reject) {
                  var i, l, record
                  for (i = 0, l = result.rows.length; i < l; ++i) {
                    record = result.rows[ i ]
                    var pre = {
                      startTime: moment.tz(record.startDateTime, api.config.formats.tz).format(api.config.formats.time),
                      startDate: moment.tz(record.startDateTime, api.config.formats.tz).format(api.config.formats.date),
                      endTime: moment.tz(record.endDateTime, api.config.formats.tz).format(api.config.formats.time),
                      endDate: moment.tz(record.endDateTime, api.config.formats.tz).format(api.config.formats.date),
                      email: record[ 'user.email' ],
                      firstName: record[ 'user.firstName' ],
                      lastName: record[ 'user.lastName' ],
                      observationDate: moment.tz(record.observationDateTime, api.config.formats.tz).format(api.config.formats.date),
                      observationTime: moment.tz(record.observationDateTime, api.config.formats.tz).format(api.config.formats.time),
                      observationDateUTC: moment.tz(record.observationDateTime, 'UTC').format(api.config.formats.date),
                      observationTimeUTC: moment.tz(record.observationDateTime, 'UTC').format(api.config.formats.time),
                      otherObservers: record.observers
                    }
                    var mid = prepareCsv(api, record)
                    var post = {
                      notes: (record.notes || '').replace(/[\n\r]+/g, ' '),
                      speciesNotes: (record.speciesNotes || '').replace(/[\n\r]+/g, ' '),
                      pictures: (record.pictures ? JSON.parse(record.pictures) || [] : []).map(function (pic) {
                        return pic.url && pic.url.split('/').slice(-1)[ 0 ] + '.jpg'
                      }).filter(function (val) {
                        return val
                      }).join(', ') || '',
                      track: record.track && record.monitoringCode + '.gpx',
                      trackId: record.track && record.track.split('/').slice(-1)[ 0 ]
                    }

                    if (api.models[modelName].associations.speciesInfo) {
                      post.species = record['speciesInfo.labelLa'] + ' | ' + record['speciesInfo.labelBg']
                      post.speciesEn = record['speciesInfo.labelLa'] + ' | ' + record['speciesInfo.labelEn']
                    }

                    result.rows[ i ] = _.assign(pre, mid, post)
                  }
                  require('csv-stringify')(result.rows, {
                    delimiter: ';',
                    header: true
                  }, function (err, csv) {
                    if (err) {
                      return reject(err)
                    }
                    switch (data.connection.extension) {
                      case 'zip':
                        return resolve({
                          csv: csv,
                          data: result.rows
                        })
                      case 'csv':
                      default:
                        return resolve(csv)
                    }
                  })
                })
              default:
                return Promise
                  .map(result.rows, function (model) {
                    return model.apiData(api)
                  })
                  .then(function (data) {
                    return {
                      count: result.count,
                      data: data
                    }
                  })
            }
          })
          .then(function (response) {
            switch (data.connection.extension) {
              case 'zip':
                return new Promise(function (resolve, reject) {
                  var archiver = require('archiver')
                  var archive = archiver.create('zip', {})
                  archive.on('error', function (err) {
                    api.log('archive error: %s', 'error', err)
                    reject(err)
                  })

                  var fs = require('fs')
                  var uuid = require('uuid')
                  var path = require('path')
                  var outputFilename = path.join(api.config.general.paths.fileupload[ 0 ], uuid.v4() + '.zip')
                  var output = fs.createWriteStream(outputFilename)
                  output.on('error', function (err) {
                    api.log('output error: %s', 'error', err)
                    reject(err)
                  })
                  output.on('finish', function () {
                    api.log('tempfile %s', 'info', outputFilename)
                    resolve(outputFilename)
                  })
                  archive.pipe(output)

                  archive.append(response.csv, { name: modelName + '.csv' })
                  var fileMap = {}

                  function appendFile (filename, id) {
                    if (!filename || filename in fileMap || id in fileMap) return
                    var idx = filename.lastIndexOf('.')
                    if (idx === -1) idx = filename.length
                    id = id || filename.substring(0, idx)
                    fileMap[ filename ] = id
                    fileMap[ id ] = filename
                    api.log('adding %s as %s', 'debug', id, filename)

                    return new Promise(function (resolve, reject) {
                      api.filestorage.get(id, function (err, stream) {
                        if (err) {
                          api.log('storage error:', 'error', err)
                          return reject(err)
                        }

                        resolve(archive.append(stream, { name: filename }))
                      })
                    })
                  }

                  Promise
                    .map(response.data, function (record) {
                      return Promise.all([
                        Promise.map(record.pictures ? record.pictures.split(', ') : [], function (picture) {
                          return Promise.resolve(appendFile(picture)).catch(function () {
                          })
                        }),
                        record.trackId && appendFile(record.track, record.trackId)
                      ])
                    })
                    .then(function () {
                      archive.finalize()
                    }, reject)
                })
              default:
                return response
            }
          })
          .then(function (response) {
            switch (data.connection.extension) {
              case 'csv':
                data.connection.rawConnection.responseHeaders.push([ 'Content-Type', 'text/csv' ])
                data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', 'attachment; filename="' + modelName + '.csv"' ])
                data.connection.sendMessage(response)
                data.toRender = false
                break
              case 'zip':
                data.connection.rawConnection.responseHeaders.push([ 'Content-Type', 'application/zip' ])
                data.connection.rawConnection.responseHeaders.push([ 'Content-Disposition', 'attachment; filename="' + modelName + '.zip"' ])
                var fs = require('fs')
                var stats = fs.statSync(response)
                var fileSize = stats[ 'size' ]
                api.servers.servers.web.sendFile(data.connection, null, fs.createReadStream(response), 'application/zip', fileSize)
                data.toRender = false
                break
              default:
                data.response = response
                return response
            }
          }).then(function () {
            next()
          })
          .catch(function (e) {
            api.log('Failure to retrieve records', 'error', e)
            next(e)
          })
      } catch (e) {
        api.log('Exception', 'error', e)
        next(e)
      }
    }
  }
}
<|endoftext|>


<commit_before>// Copyright 2018, University of Colorado Boulder

/**
 * General dialog type
 * Used to live at 'JOIST/Dialog'. Moved to 'SUN/Dialog' on 4/10/2018
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  var AlignBox = require( 'SCENERY/nodes/AlignBox' );
  var Display = require( 'SCENERY/display/Display' );
  var FullScreen = require( 'SCENERY/util/FullScreen' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var VBox = require( 'SCENERY/nodes/VBox' );
  var Panel = require( 'SUN/Panel' );
  var sun = require( 'SUN/sun' );
  var SunA11yStrings = require( 'SUN/SunA11yStrings' );
  var Tandem = require( 'TANDEM/Tandem' );
  var DialogIO = require( 'SUN/DialogIO' );
  var Path = require( 'SCENERY/nodes/Path' );
  var RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var Shape = require( 'KITE/Shape' );

  // strings
  var closeString = SunA11yStrings.close.value;

  // constants
  var CLOSE_BUTTON_WIDTH = 7;

  /**
   * @param {Node} content - The content to display inside the dialog (not including the title)
   * @param {Object} [options]
   * @constructor
   */
  function Dialog( content, options ) {

    var self = this;

    options = _.extend( {

      // Dialog-specific options
      modal: true, // {boolean} modal dialogs prevent interaction with the rest of the sim while open
      title: null, // {Node} title to be displayed at top
      titleAlign: 'center', // horizontal alignment of the title: {string} left, right or center
      xSpacing: 10, // {number} how far the title is placed to the left of the close button
      ySpacing: 10, // {number} how far the title is placed above the content,
      topMargin: 10,
      bottomMargin: 10,
      rightMargin: 10,
      leftMargin: null, // will be default set to create symmetrical gutters
      xMargin: 0, // this panel customizes its own margins
      yMargin: 0, // this panel customizes its own margins

      // {function} which sets the dialog's position in global coordinates. called as
      // layoutStrategy( dialog, simBounds, screenBounds, scale )
      layoutStrategy: Dialog.DEFAULT_LAYOUT_STRATEGY,

      // close button options
      closeButtonListener: function() { self.hide(); },
      closeButtonTouchAreaXDilation: 0,
      closeButtonTouchAreaYDilation: 0,
      closeButtonMouseAreaXDilation: 0,
      closeButtonMouseAreaYDilation: 0,

      // {function|null} called after the dialog is shown, see https://github.com/phetsims/joist/issues/478
      showCallback: null,

      // {function|null} called after the dialog is hidden, see https://github.com/phetsims/joist/issues/478
      hideCallback: null,

      // pass through to Panel options
      cornerRadius: 10, // {number} radius of the dialog's corners
      resize: true, // {boolean} whether to resize if content's size changes
      fill: 'white', // {string|Color}
      stroke: 'black', // {string|Color}
      backgroundPickable: true,
      tandem: Tandem.optional,
      phetioType: DialogIO,
      phetioReadOnly: false, // default to false so it can pass it through to the close button
      phetioState: false, // default to false so it can pass it through to the close button

      // a11y options
      tagName: 'div',
      ariaRole: 'dialog',
      focusOnCloseNode: null // {Node} receives focus on close, if null focus returns to element that had focus on open
    }, options );

    // if xMargin is specified, actually store it in right margin
    // since the default is to have symettrical left right gutters
    // the only way for client to set left margin is through optinos.leftMargin
    options.rightMargin = options.xMargin ? options.xMargin : options.rightMargin;
    options.xMargin = 0;

    // if yMargin is specified actually store it in top and bottom margins
    options.topMargin = options.yMargin ? options.yMargin : options.topMargin;
    options.bottomMargin = options.yMargin ? options.yMargin : options.bottomMargin;
    options.yMargin = 0;

    // if left margin is specified in options, use it. otherwise, set it to make the left right gutters symmetrical
    options.leftMargin = options.leftMargin ?
      options.leftMargin :
      options.rightMargin + CLOSE_BUTTON_WIDTH + options.xSpacing;

    // @private (read-only)
    this.isModal = options.modal;

    // @private
    this.showCallback = options.showCallback;
    this.hideCallback = options.hideCallback;

    // see https://github.com/phetsims/joist/issues/293
    assert && assert( this.isModal, 'Non-modal dialogs not currently supported' );

    // @private - whether the dialog is showing
    this.isShowing = false;

    // align content and title if provided
    var verticalContent = new VBox( {
      children: options.title ? [ options.title, content ] : [ content ],
      spacing: options.ySpacing,
      align: options.titleAlign
    } );

    var closeButton = new CloseButton( {

      iconLength: CLOSE_BUTTON_WIDTH,
      listener: options.closeButtonListener,

      // phet-io
      tandem: options.tandem.createTandem( 'closeButton' ),
      phetioReadOnly: options.phetioReadOnly, // match the readOnly of the Dialog
      phetioState: options.phetioState, // match the state transfer of the Dialog

      // a11y
      tagName: 'button',
      innerContent: closeString,
      accessibleFire: function() {
        self.focusActiveElement();
      }
    } );

    // touch/mouse areas for the close button
    closeButton.touchArea = closeButton.bounds.dilatedXY(
      options.closeButtonTouchAreaXDilation,
      options.closeButtonTouchAreaYDilation
    );
    closeButton.mouseArea = closeButton.bounds.dilatedXY(
      options.closeButtonMouseAreaXDilation,
      options.closeButtonMouseAreaYDilation
    );

    // @protected (a11y)
    this.closeButton = closeButton;

    // align content and close button, add left and right margins
    var contentAndClosebutton = new HBox( {
      children: [ verticalContent, closeButton ],
      spacing: options.xSpacing,
      align: 'top'
    } );

    // add top and bottom margins
    var dialogContent = new AlignBox( contentAndClosebutton, {
      leftMargin: options.leftMargin,
      rightMargin: options.rightMargin,
      topMargin: options.topMargin,
      bottomMargin: options.bottomMargin,
    } );

    Panel.call( this, dialogContent, options );

    var sim = window.phet.joist.sim;

    // @private
    this.updateLayout = function() {
      options.layoutStrategy( self, sim.boundsProperty.value, sim.screenBoundsProperty.value, sim.scaleProperty.value );
    };

    this.updateLayout();

    // @private
    this.sim = sim;

    // a11y - set the order of content for accessibility, title before content
    this.accessibleOrder = [ options.title, content ].filter( function( node ) {
      return node !== undefined;
    } );

    // a11y - set the aria-labelledby relation so that whenever focus enters the dialog the title is read
    if ( options.title ) {
      options.title.tagName && this.setAriaLabelledByNode( options.title );
    }

    // must be removed on dispose
    this.sim.resizedEmitter.addListener( this.updateLayout );

    // @private (a11y) - the active element when the dialog is shown, tracked so that focus can be restored on close
    this.activeElement = options.focusOnCloseNode || null;

    // a11y - close the dialog when pressing "escape"
    var escapeListener = {
      keydown: function( event ) {
        if ( event.keyCode === KeyboardUtil.KEY_ESCAPE ) {
          event.preventDefault();
          self.hide();
          self.focusActiveElement();
        }
        else if ( event.keyCode === KeyboardUtil.KEY_TAB && FullScreen.isFullScreen() ) {

          // prevent a particular bug in Windows 7/8.1 Firefox where focus gets trapped in the document
          // when the navigation bar is hidden and there is only one focusable element in the DOM
          // see https://bugzilla.mozilla.org/show_bug.cgi?id=910136
          var activeId = Display.focus.trail.getUniqueId();
          var noNextFocusable = AccessibilityUtil.getNextFocusable().id === activeId;
          var noPreviousFocusable = AccessibilityUtil.getPreviousFocusable().id === activeId;

          if ( noNextFocusable && noPreviousFocusable ) {
            event.preventDefault();
          }
        }
      }
    };
    this.addAccessibleInputListener( escapeListener );

    // @private - to be called on dispose()
    this.disposeDialog = function() {
      self.sim.resizedEmitter.removeListener( self.updateLayout );
      self.removeAccessibleInputListener( escapeListener );

      closeButton.dispose();

      // remove dialog content from scene graph, but don't dispose because Panel
      // needs to remove listeners on the content in its dispose()
      dialogContent.removeAllChildren();
      dialogContent.detach();
    };
  }

  sun.register( 'Dialog', Dialog );

  // @private
  Dialog.DEFAULT_LAYOUT_STRATEGY = function( dialog, simBounds, screenBounds, scale ) {

    // The size is set in the Sim.topLayer, but we need to update the location here
    dialog.center = simBounds.center.times( 1.0 / scale );
  };

  inherit( Panel, Dialog, {

    // @public
    show: function() {
      if ( !this.isShowing ) {
        window.phet.joist.sim.showPopup( this, this.isModal );
        this.isShowing = true;

        // a11y - store the currently active element before hiding all other accessible content
        // so that the active element isn't blurred
        this.activeElement = this.activeElement || Display.focusedNode;
        this.setAccessibleViewsVisible( false );

        // In case the window size has changed since the dialog was hidden, we should try layout out again.
        // See https://github.com/phetsims/joist/issues/362
        this.updateLayout();

        // Do this last
        this.showCallback && this.showCallback();
      }
    },

    /**
     * Hide the dialog.  If you create a new dialog next time you show(), be sure to dispose this
     * dialog instead.
     * @public
     */
    hide: function() {
      if ( this.isShowing ) {

        window.phet.joist.sim.hidePopup( this, this.isModal );
        this.isShowing = false;

        // a11y - when the dialog is hidden, make all ScreenView content visible to assistive technology
        this.setAccessibleViewsVisible( true );

        // Do this last
        this.hideCallback && this.hideCallback();
      }
    },

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.hide();
      this.disposeDialog();
      Panel.prototype.dispose.call( this );
    },

    /**
     * Hide or show all accessible content related to the sim ScreenViews, navigation bar, and alert content. Instead
     * of using setVisible, we have to remove the subtree of accessible content from each view element in order to
     * prevent an IE11 bug where content remains invisible in the accessibility tree, see
     * https://github.com/phetsims/john-travoltage/issues/247
     *
     * @param {boolean} visible
     */
    setAccessibleViewsVisible: function( visible ) {
      for ( var i = 0; i < this.sim.screens.length; i++ ) {
        this.sim.screens[ i ].view.accessibleContentDisplayed = visible;
      }
      this.sim.navigationBar.accessibleContentDisplayed = visible;
      this.sim.homeScreen && this.sim.homeScreen.view.setAccessibleContentDisplayed( visible );

      // workaround for a strange Edge bug where this child of the navigation bar remains visible,
      // see https://github.com/phetsims/a11y-research/issues/30
      if ( this.sim.navigationBar.keyboardHelpButton ) {
        this.sim.navigationBar.keyboardHelpButton.accessibleVisible = visible;
      }
    },

    /**
     * If there is an active element, focus it.  Should almost always be called after the Dialog has been closed.
     *
     * @public
     * @a11y
     */
    focusActiveElement: function() {
      this.activeElement && this.activeElement.focus();
    }
  } );

  /**
   * The close button for Dialog
   * A flat x
   *
   * @param {Object} [options] - see RectangularPushButton
   * @constructor
   */
  function CloseButton( options ) {
    options = _.extend( {
      iconLength: 7,
      baseColor: 'transparent',
      buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
      xMargin: 0,
      yMargin: 0,
      listener: null // {function} called when the button is pressed
    }, options );

    // close button shape, an 'X'
    var closeButtonShape = new Shape()
      .moveTo( -options.iconLength, -options.iconLength )
      .lineTo( options.iconLength, options.iconLength )
      .moveTo( options.iconLength, -options.iconLength )
      .lineTo( -options.iconLength, options.iconLength );

    options.content = new Path( closeButtonShape, {
      stroke: 'black',
      lineCap: 'round',
      lineWidth: 2
    } );

    RectangularPushButton.call( this, options );
  }

  sun.register( 'Dialog.CloseButton', CloseButton );

  inherit( RectangularPushButton, CloseButton );

  return Dialog;
} );<commit_msg>alphabetize requires<commit_after>// Copyright 2018, University of Colorado Boulder

/**
 * General dialog type
 * Used to live at 'JOIST/Dialog'. Moved to 'SUN/Dialog' on 4/10/2018
 *
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Andrea Lin
 * @author Chris Malley (PixelZoom, Inc.)
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessibilityUtil = require( 'SCENERY/accessibility/AccessibilityUtil' );
  var AlignBox = require( 'SCENERY/nodes/AlignBox' );
  var DialogIO = require( 'SUN/DialogIO' );
  var Display = require( 'SCENERY/display/Display' );
  var FullScreen = require( 'SCENERY/util/FullScreen' );
  var HBox = require( 'SCENERY/nodes/HBox' );
  var inherit = require( 'PHET_CORE/inherit' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var Panel = require( 'SUN/Panel' );
  var Path = require( 'SCENERY/nodes/Path' );
  var RectangularButtonView = require( 'SUN/buttons/RectangularButtonView' );
  var RectangularPushButton = require( 'SUN/buttons/RectangularPushButton' );
  var Shape = require( 'KITE/Shape' );
  var sun = require( 'SUN/sun' );
  var SunA11yStrings = require( 'SUN/SunA11yStrings' );
  var Tandem = require( 'TANDEM/Tandem' );
  var VBox = require( 'SCENERY/nodes/VBox' );

  // strings
  var closeString = SunA11yStrings.close.value;

  // constants
  var CLOSE_BUTTON_WIDTH = 7;

  /**
   * @param {Node} content - The content to display inside the dialog (not including the title)
   * @param {Object} [options]
   * @constructor
   */
  function Dialog( content, options ) {

    var self = this;

    options = _.extend( {

      // Dialog-specific options
      modal: true, // {boolean} modal dialogs prevent interaction with the rest of the sim while open
      title: null, // {Node} title to be displayed at top
      titleAlign: 'center', // horizontal alignment of the title: {string} left, right or center
      xSpacing: 10, // {number} how far the title is placed to the left of the close button
      ySpacing: 10, // {number} how far the title is placed above the content,
      topMargin: 10,
      bottomMargin: 10,
      rightMargin: 10,
      leftMargin: null, // will be default set to create symmetrical gutters
      xMargin: 0, // this panel customizes its own margins
      yMargin: 0, // this panel customizes its own margins

      // {function} which sets the dialog's position in global coordinates. called as
      // layoutStrategy( dialog, simBounds, screenBounds, scale )
      layoutStrategy: Dialog.DEFAULT_LAYOUT_STRATEGY,

      // close button options
      closeButtonListener: function() { self.hide(); },
      closeButtonTouchAreaXDilation: 0,
      closeButtonTouchAreaYDilation: 0,
      closeButtonMouseAreaXDilation: 0,
      closeButtonMouseAreaYDilation: 0,

      // {function|null} called after the dialog is shown, see https://github.com/phetsims/joist/issues/478
      showCallback: null,

      // {function|null} called after the dialog is hidden, see https://github.com/phetsims/joist/issues/478
      hideCallback: null,

      // pass through to Panel options
      cornerRadius: 10, // {number} radius of the dialog's corners
      resize: true, // {boolean} whether to resize if content's size changes
      fill: 'white', // {string|Color}
      stroke: 'black', // {string|Color}
      backgroundPickable: true,
      tandem: Tandem.optional,
      phetioType: DialogIO,
      phetioReadOnly: false, // default to false so it can pass it through to the close button
      phetioState: false, // default to false so it can pass it through to the close button

      // a11y options
      tagName: 'div',
      ariaRole: 'dialog',
      focusOnCloseNode: null // {Node} receives focus on close, if null focus returns to element that had focus on open
    }, options );

    // if xMargin is specified, actually store it in right margin
    // since the default is to have symettrical left right gutters
    // the only way for client to set left margin is through optinos.leftMargin
    options.rightMargin = options.xMargin ? options.xMargin : options.rightMargin;
    options.xMargin = 0;

    // if yMargin is specified actually store it in top and bottom margins
    options.topMargin = options.yMargin ? options.yMargin : options.topMargin;
    options.bottomMargin = options.yMargin ? options.yMargin : options.bottomMargin;
    options.yMargin = 0;

    // if left margin is specified in options, use it. otherwise, set it to make the left right gutters symmetrical
    options.leftMargin = options.leftMargin ?
      options.leftMargin :
      options.rightMargin + CLOSE_BUTTON_WIDTH + options.xSpacing;

    // @private (read-only)
    this.isModal = options.modal;

    // @private
    this.showCallback = options.showCallback;
    this.hideCallback = options.hideCallback;

    // see https://github.com/phetsims/joist/issues/293
    assert && assert( this.isModal, 'Non-modal dialogs not currently supported' );

    // @private - whether the dialog is showing
    this.isShowing = false;

    // align content and title if provided
    var verticalContent = new VBox( {
      children: options.title ? [ options.title, content ] : [ content ],
      spacing: options.ySpacing,
      align: options.titleAlign
    } );

    var closeButton = new CloseButton( {

      iconLength: CLOSE_BUTTON_WIDTH,
      listener: options.closeButtonListener,

      // phet-io
      tandem: options.tandem.createTandem( 'closeButton' ),
      phetioReadOnly: options.phetioReadOnly, // match the readOnly of the Dialog
      phetioState: options.phetioState, // match the state transfer of the Dialog

      // a11y
      tagName: 'button',
      innerContent: closeString,
      accessibleFire: function() {
        self.focusActiveElement();
      }
    } );

    // touch/mouse areas for the close button
    closeButton.touchArea = closeButton.bounds.dilatedXY(
      options.closeButtonTouchAreaXDilation,
      options.closeButtonTouchAreaYDilation
    );
    closeButton.mouseArea = closeButton.bounds.dilatedXY(
      options.closeButtonMouseAreaXDilation,
      options.closeButtonMouseAreaYDilation
    );

    // @protected (a11y)
    this.closeButton = closeButton;

    // align content and close button, add left and right margins
    var contentAndClosebutton = new HBox( {
      children: [ verticalContent, closeButton ],
      spacing: options.xSpacing,
      align: 'top'
    } );

    // add top and bottom margins
    var dialogContent = new AlignBox( contentAndClosebutton, {
      leftMargin: options.leftMargin,
      rightMargin: options.rightMargin,
      topMargin: options.topMargin,
      bottomMargin: options.bottomMargin,
    } );

    Panel.call( this, dialogContent, options );

    var sim = window.phet.joist.sim;

    // @private
    this.updateLayout = function() {
      options.layoutStrategy( self, sim.boundsProperty.value, sim.screenBoundsProperty.value, sim.scaleProperty.value );
    };

    this.updateLayout();

    // @private
    this.sim = sim;

    // a11y - set the order of content for accessibility, title before content
    this.accessibleOrder = [ options.title, content ].filter( function( node ) {
      return node !== undefined;
    } );

    // a11y - set the aria-labelledby relation so that whenever focus enters the dialog the title is read
    if ( options.title ) {
      options.title.tagName && this.setAriaLabelledByNode( options.title );
    }

    // must be removed on dispose
    this.sim.resizedEmitter.addListener( this.updateLayout );

    // @private (a11y) - the active element when the dialog is shown, tracked so that focus can be restored on close
    this.activeElement = options.focusOnCloseNode || null;

    // a11y - close the dialog when pressing "escape"
    var escapeListener = {
      keydown: function( event ) {
        if ( event.keyCode === KeyboardUtil.KEY_ESCAPE ) {
          event.preventDefault();
          self.hide();
          self.focusActiveElement();
        }
        else if ( event.keyCode === KeyboardUtil.KEY_TAB && FullScreen.isFullScreen() ) {

          // prevent a particular bug in Windows 7/8.1 Firefox where focus gets trapped in the document
          // when the navigation bar is hidden and there is only one focusable element in the DOM
          // see https://bugzilla.mozilla.org/show_bug.cgi?id=910136
          var activeId = Display.focus.trail.getUniqueId();
          var noNextFocusable = AccessibilityUtil.getNextFocusable().id === activeId;
          var noPreviousFocusable = AccessibilityUtil.getPreviousFocusable().id === activeId;

          if ( noNextFocusable && noPreviousFocusable ) {
            event.preventDefault();
          }
        }
      }
    };
    this.addAccessibleInputListener( escapeListener );

    // @private - to be called on dispose()
    this.disposeDialog = function() {
      self.sim.resizedEmitter.removeListener( self.updateLayout );
      self.removeAccessibleInputListener( escapeListener );

      closeButton.dispose();

      // remove dialog content from scene graph, but don't dispose because Panel
      // needs to remove listeners on the content in its dispose()
      dialogContent.removeAllChildren();
      dialogContent.detach();
    };
  }

  sun.register( 'Dialog', Dialog );

  // @private
  Dialog.DEFAULT_LAYOUT_STRATEGY = function( dialog, simBounds, screenBounds, scale ) {

    // The size is set in the Sim.topLayer, but we need to update the location here
    dialog.center = simBounds.center.times( 1.0 / scale );
  };

  inherit( Panel, Dialog, {

    // @public
    show: function() {
      if ( !this.isShowing ) {
        window.phet.joist.sim.showPopup( this, this.isModal );
        this.isShowing = true;

        // a11y - store the currently active element before hiding all other accessible content
        // so that the active element isn't blurred
        this.activeElement = this.activeElement || Display.focusedNode;
        this.setAccessibleViewsVisible( false );

        // In case the window size has changed since the dialog was hidden, we should try layout out again.
        // See https://github.com/phetsims/joist/issues/362
        this.updateLayout();

        // Do this last
        this.showCallback && this.showCallback();
      }
    },

    /**
     * Hide the dialog.  If you create a new dialog next time you show(), be sure to dispose this
     * dialog instead.
     * @public
     */
    hide: function() {
      if ( this.isShowing ) {

        window.phet.joist.sim.hidePopup( this, this.isModal );
        this.isShowing = false;

        // a11y - when the dialog is hidden, make all ScreenView content visible to assistive technology
        this.setAccessibleViewsVisible( true );

        // Do this last
        this.hideCallback && this.hideCallback();
      }
    },

    /**
     * Make eligible for garbage collection.
     * @public
     */
    dispose: function() {
      this.hide();
      this.disposeDialog();
      Panel.prototype.dispose.call( this );
    },

    /**
     * Hide or show all accessible content related to the sim ScreenViews, navigation bar, and alert content. Instead
     * of using setVisible, we have to remove the subtree of accessible content from each view element in order to
     * prevent an IE11 bug where content remains invisible in the accessibility tree, see
     * https://github.com/phetsims/john-travoltage/issues/247
     *
     * @param {boolean} visible
     */
    setAccessibleViewsVisible: function( visible ) {
      for ( var i = 0; i < this.sim.screens.length; i++ ) {
        this.sim.screens[ i ].view.accessibleContentDisplayed = visible;
      }
      this.sim.navigationBar.accessibleContentDisplayed = visible;
      this.sim.homeScreen && this.sim.homeScreen.view.setAccessibleContentDisplayed( visible );

      // workaround for a strange Edge bug where this child of the navigation bar remains visible,
      // see https://github.com/phetsims/a11y-research/issues/30
      if ( this.sim.navigationBar.keyboardHelpButton ) {
        this.sim.navigationBar.keyboardHelpButton.accessibleVisible = visible;
      }
    },

    /**
     * If there is an active element, focus it.  Should almost always be called after the Dialog has been closed.
     *
     * @public
     * @a11y
     */
    focusActiveElement: function() {
      this.activeElement && this.activeElement.focus();
    }
  } );

  /**
   * The close button for Dialog
   * A flat x
   *
   * @param {Object} [options] - see RectangularPushButton
   * @constructor
   */
  function CloseButton( options ) {
    options = _.extend( {
      iconLength: 7,
      baseColor: 'transparent',
      buttonAppearanceStrategy: RectangularButtonView.FlatAppearanceStrategy,
      xMargin: 0,
      yMargin: 0,
      listener: null // {function} called when the button is pressed
    }, options );

    // close button shape, an 'X'
    var closeButtonShape = new Shape()
      .moveTo( -options.iconLength, -options.iconLength )
      .lineTo( options.iconLength, options.iconLength )
      .moveTo( options.iconLength, -options.iconLength )
      .lineTo( -options.iconLength, options.iconLength );

    options.content = new Path( closeButtonShape, {
      stroke: 'black',
      lineCap: 'round',
      lineWidth: 2
    } );

    RectangularPushButton.call( this, options );
  }

  sun.register( 'Dialog.CloseButton', CloseButton );

  inherit( RectangularPushButton, CloseButton );

  return Dialog;
} );<|endoftext|>