github.com/qubitproducts/logspray@v0.2.14/server/swagger-ui/src/main/javascript/view/OperationView.js (about)

     1  'use strict';
     2  
     3  SwaggerUi.Views.OperationView = Backbone.View.extend({
     4    invocationUrl: null,
     5  
     6    events: {
     7      'submit .sandbox'         : 'submitOperation',
     8      'click .submit'           : 'submitOperation',
     9      'click .response_hider'   : 'hideResponse',
    10      'click .toggleOperation'  : 'toggleOperationContent',
    11      'mouseenter .api-ic'      : 'mouseEnter',
    12      'dblclick .curl'          : 'selectText',
    13      'change [name=responseContentType]' : 'showSnippet'
    14    },
    15  
    16    initialize: function(opts) {
    17      opts = opts || {};
    18      this.router = opts.router;
    19      this.auths = opts.auths;
    20      this.parentId = this.model.parentId;
    21      this.nickname = this.model.nickname;
    22      this.model.encodedParentId = encodeURIComponent(this.parentId);
    23  
    24      if (opts.swaggerOptions) {
    25        this.model.defaultRendering = opts.swaggerOptions.defaultModelRendering;
    26  
    27        if (opts.swaggerOptions.showRequestHeaders) {
    28          this.model.showRequestHeaders = true;
    29        }
    30      }
    31      return this;
    32    },
    33  
    34    selectText: function(event) {
    35      var doc = document,
    36          text = event.target.firstChild,
    37          range,
    38          selection;
    39      if (doc.body.createTextRange) {
    40        range = document.body.createTextRange();
    41        range.moveToElementText(text);
    42        range.select();
    43      } else if (window.getSelection) {
    44        selection = window.getSelection();
    45        range = document.createRange();
    46        range.selectNodeContents(text);
    47        selection.removeAllRanges();
    48        selection.addRange(range);
    49      }
    50    },
    51  
    52    mouseEnter: function(e) {
    53      var elem = $(this.el).find('.content');
    54      var x = e.pageX;
    55      var y = e.pageY;
    56      var scX = $(window).scrollLeft();
    57      var scY = $(window).scrollTop();
    58      var scMaxX = scX + $(window).width();
    59      var scMaxY = scY + $(window).height();
    60      var wd = elem.width();
    61      var hgh = elem.height();
    62  
    63      if (x + wd > scMaxX) {
    64        x = scMaxX - wd;
    65      }
    66  
    67      if (x < scX) {
    68        x = scX;
    69      }
    70  
    71      if (y + hgh > scMaxY) {
    72        y = scMaxY - hgh;
    73      }
    74  
    75      if (y < scY) {
    76        y = scY;
    77      }
    78  
    79      var pos = {};
    80      pos.top = y;
    81      pos.left = x;
    82      elem.css(pos);
    83    },
    84  
    85    // Note: copied from CoffeeScript compiled file
    86    // TODO: refactor
    87    render: function() {
    88      var a, auth, auths, code, contentTypeModel, isMethodSubmissionSupported, k, key, l, len, len1, len2, len3, len4, m, modelAuths, n, o, p, param, q, ref, ref1, ref2, ref3, ref4, ref5, responseContentTypeView, responseSignatureView, schema, schemaObj, scopeIndex, signatureModel, statusCode, successResponse, type, v, value, produces, isXML, isJSON;
    89      isMethodSubmissionSupported = jQuery.inArray(this.model.method, this.model.supportedSubmitMethods()) >= 0;
    90      if (!isMethodSubmissionSupported) {
    91        this.model.isReadOnly = true;
    92      }
    93      this.model.description = this.model.description || this.model.notes;
    94      this.model.oauth = null;
    95      modelAuths = this.model.authorizations || this.model.security;
    96      if (modelAuths) {
    97        if (Array.isArray(modelAuths)) {
    98          for (l = 0, len = modelAuths.length; l < len; l++) {
    99            auths = modelAuths[l];
   100            for (key in auths) {
   101              for (a in this.auths) {
   102                auth = this.auths[a];
   103                if (key === auth.name) {
   104                  if (auth.type === 'oauth2') {
   105                    this.model.oauth = {};
   106                    this.model.oauth.scopes = [];
   107                    ref1 = auth.value.scopes;
   108                    for (k in ref1) {
   109                      v = ref1[k];
   110                      scopeIndex = auths[key].indexOf(k);
   111                      if (scopeIndex >= 0) {
   112                        o = {
   113                          scope: k,
   114                          description: v
   115                        };
   116                        this.model.oauth.scopes.push(o);
   117                      }
   118                    }
   119                  }
   120                }
   121              }
   122            }
   123          }
   124        } else {
   125          for (k in modelAuths) {
   126            v = modelAuths[k];
   127            if (k === 'oauth2') {
   128              if (this.model.oauth === null) {
   129                this.model.oauth = {};
   130              }
   131              if (this.model.oauth.scopes === void 0) {
   132                this.model.oauth.scopes = [];
   133              }
   134              for (m = 0, len1 = v.length; m < len1; m++) {
   135                o = v[m];
   136                this.model.oauth.scopes.push(o);
   137              }
   138            }
   139          }
   140        }
   141      }
   142      if (typeof this.model.responses !== 'undefined') {
   143        this.model.responseMessages = [];
   144        ref2 = this.model.responses;
   145        for (code in ref2) {
   146          value = ref2[code];
   147          schema = null;
   148          schemaObj = this.model.responses[code].schema;
   149          if (schemaObj && schemaObj.$ref) {
   150            schema = schemaObj.$ref;
   151            if (schema.indexOf('#/definitions/') !== -1) {
   152              schema = schema.replace(/^.*#\/definitions\//, '');
   153            }
   154          }
   155          this.model.responseMessages.push({
   156            code: code,
   157            message: value.description,
   158            responseModel: schema,
   159            headers: value.headers,
   160            schema: schemaObj
   161          });
   162        }
   163      }
   164      if (typeof this.model.responseMessages === 'undefined') {
   165        this.model.responseMessages = [];
   166      }
   167      signatureModel = null;
   168      produces = this.model.produces;
   169      isXML = this.contains(produces, 'xml');
   170      isJSON = isXML ? this.contains(produces, 'json') : true;
   171  
   172      if (this.model.successResponse) {
   173        successResponse = this.model.successResponse;
   174        for (key in successResponse) {
   175          value = successResponse[key];
   176          this.model.successCode = key;
   177          if (typeof value === 'object' && typeof value.createJSONSample === 'function') {
   178            this.model.successDescription = value.description;
   179            this.model.headers = this.parseResponseHeaders(value.headers);
   180            signatureModel = {
   181              sampleJSON: isJSON ? JSON.stringify(SwaggerUi.partials.signature.createJSONSample(value), void 0, 2) : false,
   182              isParam: false,
   183              sampleXML: isXML ? SwaggerUi.partials.signature.createXMLSample(value.name, value.definition, value.models) : false,
   184              signature: SwaggerUi.partials.signature.getModelSignature(value.name, value.definition, value.models, value.modelPropertyMacro)
   185            };
   186          } else {
   187            signatureModel = {
   188              signature: SwaggerUi.partials.signature.getPrimitiveSignature(value)
   189            };
   190          }
   191        }
   192      } else if (this.model.responseClassSignature && this.model.responseClassSignature !== 'string') {
   193        signatureModel = {
   194          sampleJSON: this.model.responseSampleJSON,
   195          isParam: false,
   196          signature: this.model.responseClassSignature
   197        };
   198      }
   199      $(this.el).html(Handlebars.templates.operation(this.model));
   200      if (signatureModel) {
   201        signatureModel.defaultRendering = this.model.defaultRendering;
   202        responseSignatureView = new SwaggerUi.Views.SignatureView({
   203          model: signatureModel,
   204          router: this.router,
   205          tagName: 'div'
   206        });
   207        $('.model-signature', $(this.el)).append(responseSignatureView.render().el);
   208      } else {
   209        this.model.responseClassSignature = 'string';
   210        $('.model-signature', $(this.el)).html(this.model.type);
   211      }
   212      contentTypeModel = {
   213        isParam: false
   214      };
   215      contentTypeModel.consumes = this.model.consumes;
   216      contentTypeModel.produces = this.model.produces;
   217      ref3 = this.model.parameters;
   218      for (n = 0, len2 = ref3.length; n < len2; n++) {
   219        param = ref3[n];
   220        type = param.type || param.dataType || '';
   221        if (typeof type === 'undefined') {
   222          schema = param.schema;
   223          if (schema && schema.$ref) {
   224            ref = schema.$ref;
   225            if (ref.indexOf('#/definitions/') === 0) {
   226              type = ref.substring('#/definitions/'.length);
   227            } else {
   228              type = ref;
   229            }
   230          }
   231        }
   232        if (type && type.toLowerCase() === 'file') {
   233          if (!contentTypeModel.consumes) {
   234            contentTypeModel.consumes = 'multipart/form-data';
   235          }
   236        }
   237        param.type = type;
   238      }
   239      responseContentTypeView = new SwaggerUi.Views.ResponseContentTypeView({
   240        model: contentTypeModel,
   241        router: this.router
   242      });
   243      $('.response-content-type', $(this.el)).append(responseContentTypeView.render().el);
   244      ref4 = this.model.parameters;
   245      for (p = 0, len3 = ref4.length; p < len3; p++) {
   246        param = ref4[p];
   247        this.addParameter(param, contentTypeModel.consumes);
   248      }
   249      ref5 = this.model.responseMessages;
   250      for (q = 0, len4 = ref5.length; q < len4; q++) {
   251        statusCode = ref5[q];
   252        statusCode.isXML = isXML;
   253        statusCode.isJSON = isJSON;
   254        if (!_.isUndefined(statusCode.headers)) {
   255          statusCode.headers = this.parseHeadersType(statusCode.headers);
   256        }
   257        this.addStatusCode(statusCode);
   258      }
   259  
   260      if (Array.isArray(this.model.security)) {
   261        var authsModel = SwaggerUi.utils.parseSecurityDefinitions(this.model.security, this.model.parent.securityDefinitions);
   262  
   263        authsModel.isLogout = !_.isEmpty(this.model.clientAuthorizations.authz);
   264        this.authView = new SwaggerUi.Views.AuthButtonView({
   265          data: authsModel,
   266          router: this.router,
   267          isOperation: true,
   268          model: {
   269            scopes: authsModel.scopes
   270          }
   271        });
   272        this.$('.authorize-wrapper').append(this.authView.render().el);
   273      }
   274  
   275      this.showSnippet();
   276      return this;
   277    },
   278  
   279    parseHeadersType: function (headers) {
   280      var map = {
   281        'string': {
   282          'date-time': 'dateTime',
   283          'date'     : 'date'
   284        }
   285      };
   286  
   287      _.forEach(headers, function (header) {
   288        var value;
   289        header = header || {};
   290        value = map[header.type] && map[header.type][header.format];
   291        if (!_.isUndefined(value)) {
   292          header.type = value;
   293        }
   294      });
   295  
   296      return headers;
   297    },
   298  
   299    contains: function (produces, type) {
   300      return produces.filter(function (val) {
   301        if (val.indexOf(type) > -1) {
   302          return true;
   303        }
   304      }).length;
   305    },
   306  
   307    parseResponseHeaders: function (data) {
   308      var HEADERS_SEPARATOR = '; ';
   309      var headers = _.clone(data);
   310  
   311      _.forEach(headers, function (header) {
   312        var other = [];
   313        _.forEach(header, function (value, key) {
   314          var properties = ['type', 'description'];
   315          if (properties.indexOf(key.toLowerCase()) === -1) {
   316            other.push(key + ': ' + value);
   317          }
   318        });
   319  
   320        other.join(HEADERS_SEPARATOR);
   321        header.other = other;
   322      });
   323  
   324      return headers;
   325    },
   326  
   327    addParameter: function(param, consumes) {
   328      // Render a parameter
   329      param.consumes = consumes;
   330      param.defaultRendering = this.model.defaultRendering;
   331  
   332      // Copy this param JSON spec so that it will be available for JsonEditor
   333      if(param.schema){
   334        $.extend(true, param.schema, this.model.definitions[param.type]);
   335        param.schema.definitions = this.model.definitions;
   336        // This is required for JsonEditor to display the root properly
   337        if(!param.schema.type){
   338          param.schema.type = 'object';
   339        }
   340        // This is the title that will be used by JsonEditor for the root
   341        // Since we already display the parameter's name in the Parameter column
   342        // We set this to space, we can't set it to null or space otherwise JsonEditor
   343        // will replace it with the text "root" which won't look good on screen
   344        if(!param.schema.title){
   345          param.schema.title = ' ';
   346        }
   347      }
   348  
   349      var paramView = new SwaggerUi.Views.ParameterView({
   350        model: param,
   351        tagName: 'tr',
   352        readOnly: this.model.isReadOnly,
   353        swaggerOptions: this.options.swaggerOptions
   354      });
   355      $('.operation-params', $(this.el)).append(paramView.render().el);
   356    },
   357  
   358    addStatusCode: function(statusCode) {
   359      // Render status codes
   360      statusCode.defaultRendering = this.model.defaultRendering;
   361      var statusCodeView = new SwaggerUi.Views.StatusCodeView({
   362        model: statusCode,
   363        tagName: 'tr',
   364        router: this.router
   365      });
   366      $('.operation-status', $(this.el)).append(statusCodeView.render().el);
   367    },
   368  
   369    // Note: copied from CoffeeScript compiled file
   370    // TODO: redactor
   371    submitOperation: function(e) {
   372      var error_free, form, isFileUpload, map, opts;
   373      if (e !== null) {
   374        e.preventDefault();
   375      }
   376      form = $('.sandbox', $(this.el));
   377      error_free = true;
   378      form.find('input.required').each(function() {
   379        $(this).removeClass('error');
   380        if (jQuery.trim($(this).val()) === '') {
   381          $(this).addClass('error');
   382          $(this).wiggle({
   383            callback: (function(_this) {
   384              return function() {
   385                $(_this).focus();
   386              };
   387            })(this)
   388          });
   389          error_free = false;
   390        }
   391      });
   392      form.find('textarea.required:visible').each(function() {
   393        $(this).removeClass('error');
   394        if (jQuery.trim($(this).val()) === '') {
   395          $(this).addClass('error');
   396          $(this).wiggle({
   397            callback: (function(_this) {
   398              return function() {
   399                return $(_this).focus();
   400              };
   401            })(this)
   402          });
   403          error_free = false;
   404        }
   405      });
   406      form.find('select.required').each(function() {
   407        $(this).removeClass('error');
   408        if (this.selectedIndex === -1) {
   409          $(this).addClass('error');
   410          $(this).wiggle({
   411            callback: (function(_this) {
   412              return function() {
   413                $(_this).focus();
   414              };
   415            })(this)
   416          });
   417          error_free = false;
   418        }
   419      });
   420      if (error_free) {
   421        map = this.getInputMap(form);
   422        isFileUpload = this.isFileUpload(form);
   423        opts = {
   424          parent: this
   425        };
   426        if (this.options.swaggerOptions) {
   427          for(var key in this.options.swaggerOptions) {
   428            opts[key] = this.options.swaggerOptions[key];
   429          }
   430        }
   431  
   432        var pi;
   433        for(pi = 0; pi < this.model.parameters.length; pi++){
   434          var p = this.model.parameters[pi];
   435          if( p.jsonEditor && p.jsonEditor.isEnabled()){
   436            var json = p.jsonEditor.getValue();
   437            map[p.name] = JSON.stringify(json);
   438          }
   439        }
   440  
   441        opts.responseContentType = $('div select[name=responseContentType]', $(this.el)).val();
   442        opts.requestContentType = $('div select[name=parameterContentType]', $(this.el)).val();
   443        $('.response_throbber', $(this.el)).show();
   444        if (isFileUpload) {
   445          $('.request_url', $(this.el)).html('<pre></pre>');
   446          $('.request_url pre', $(this.el)).text(this.invocationUrl);
   447  
   448          opts.useJQuery = true;
   449          map.parameterContentType = 'multipart/form-data';
   450          this.map = map;
   451          return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);
   452        } else {
   453          this.map = map;
   454          return this.model.execute(map, opts, this.showCompleteStatus, this.showErrorStatus, this);
   455        }
   456      }
   457    },
   458  
   459    getInputMap: function (form) {
   460      var map, ref1, l, len, o, ref2, m, len1, val, ref3, n, len2;
   461      map = {};
   462      ref1 = form.find('input');
   463      for (l = 0, len = ref1.length; l < len; l++) {
   464        o = ref1[l];
   465        if ((o.value !== null) && jQuery.trim(o.value).length > 0) {
   466          map[o.name] = o.value;
   467        }
   468        if (o.type === 'file') {
   469          map[o.name] = o.files[0];
   470        }
   471      }
   472      ref2 = form.find('textarea');
   473      for (m = 0, len1 = ref2.length; m < len1; m++) {
   474        o = ref2[m];
   475        val = this.getTextAreaValue(o);
   476        if ((val !== null) && jQuery.trim(val).length > 0) {
   477          map[o.name] = val;
   478        }
   479      }
   480      ref3 = form.find('select');
   481      for (n = 0, len2 = ref3.length; n < len2; n++) {
   482        o = ref3[n];
   483        val = this.getSelectedValue(o);
   484        if ((val !== null) && jQuery.trim(val).length > 0) {
   485          map[o.name] = val;
   486        }
   487      }
   488      return map;
   489    },
   490  
   491    isFileUpload: function (form) {
   492      var ref1, l, len, o;
   493      var isFileUpload = false;
   494      ref1 = form.find('input');
   495      for (l = 0, len = ref1.length; l < len; l++) {
   496        o = ref1[l];
   497        if (o.type === 'file') {
   498          isFileUpload = true;
   499        }
   500      }
   501      return isFileUpload;
   502    },
   503  
   504    success: function(response, parent) {
   505      parent.showCompleteStatus(response);
   506    },
   507  
   508    // wraps a jquery response as a shred response
   509    wrap: function(data) {
   510      var h, headerArray, headers, i, l, len, o;
   511      headers = {};
   512      headerArray = data.getAllResponseHeaders().split('\r');
   513      for (l = 0, len = headerArray.length; l < len; l++) {
   514        i = headerArray[l];
   515        h = i.match(/^([^:]*?):(.*)$/);
   516        if (!h) {
   517          h = [];
   518        }
   519        h.shift();
   520        if (h[0] !== void 0 && h[1] !== void 0) {
   521          headers[h[0].trim()] = h[1].trim();
   522        }
   523      }
   524      o = {};
   525      o.content = {};
   526      o.content.data = data.responseText;
   527      o.headers = headers;
   528      o.request = {};
   529      o.request.url = this.invocationUrl;
   530      o.status = data.status;
   531      return o;
   532    },
   533  
   534    getSelectedValue: function(select) {
   535      if (!select.multiple) {
   536        return select.value;
   537      } else {
   538        var options = [];
   539        for (var l = 0, len = select.options.length; l < len; l++) {
   540          var opt = select.options[l];
   541          if (opt.selected) {
   542            options.push(opt.value);
   543          }
   544        }
   545        if (options.length > 0) {
   546          return options;
   547        } else {
   548          return null;
   549        }
   550      }
   551    },
   552  
   553    // handler for hide response link
   554    hideResponse: function(e) {
   555      if (e) { e.preventDefault(); }
   556      $('.response', $(this.el)).slideUp();
   557      $('.response_hider', $(this.el)).fadeOut();
   558    },
   559  
   560    // Show response from server
   561    showResponse: function(response) {
   562      var prettyJson = JSON.stringify(response, null, '\t').replace(/\n/g, '<br>');
   563      $('.response_body', $(this.el)).html(_.escape(prettyJson));
   564    },
   565  
   566    // Show error from server
   567    showErrorStatus: function(data, parent) {
   568      parent.showStatus(data);
   569    },
   570  
   571    // show the status codes
   572    showCompleteStatus: function(data, parent){
   573      parent.showStatus(data);
   574    },
   575  
   576    // Adapted from http://stackoverflow.com/a/2893259/454004
   577    // Note: directly ported from CoffeeScript
   578    // TODO: Cleanup CoffeeScript artifacts
   579    formatXml: function(xml) {
   580      var contexp, fn, formatted, indent, l, lastType, len, lines, ln, pad, reg, transitions, wsexp;
   581      reg = /(>)(<)(\/*)/g;
   582      wsexp = /[ ]*(.*)[ ]+\n/g;
   583      contexp = /(<.+>)(.+\n)/g;
   584      xml = xml.replace(/\r\n/g, '\n').replace(reg, '$1\n$2$3').replace(wsexp, '$1\n').replace(contexp, '$1\n$2');
   585      pad = 0;
   586      formatted = '';
   587      lines = xml.split('\n');
   588      indent = 0;
   589      lastType = 'other';
   590      transitions = {
   591        'single->single': 0,
   592        'single->closing': -1,
   593        'single->opening': 0,
   594        'single->other': 0,
   595        'closing->single': 0,
   596        'closing->closing': -1,
   597        'closing->opening': 0,
   598        'closing->other': 0,
   599        'opening->single': 1,
   600        'opening->closing': 0,
   601        'opening->opening': 1,
   602        'opening->other': 1,
   603        'other->single': 0,
   604        'other->closing': -1,
   605        'other->opening': 0,
   606        'other->other': 0
   607      };
   608      fn = function(ln) {
   609        var fromTo, j, key, padding, type, types, value;
   610        types = {
   611          single: Boolean(ln.match(/<.+\/>/)),
   612          closing: Boolean(ln.match(/<\/.+>/)),
   613          opening: Boolean(ln.match(/<[^!?].*>/))
   614        };
   615        type = ((function() {
   616          var results;
   617          results = [];
   618          for (key in types) {
   619            value = types[key];
   620            if (value) {
   621              results.push(key);
   622            }
   623          }
   624          return results;
   625        })())[0];
   626        type = type === void 0 ? 'other' : type;
   627        fromTo = lastType + '->' + type;
   628        lastType = type;
   629        padding = '';
   630        indent += transitions[fromTo];
   631        padding = ((function() {
   632          var m, ref1, results;
   633          results = [];
   634          for (j = m = 0, ref1 = indent; 0 <= ref1 ? m < ref1 : m > ref1; j = 0 <= ref1 ? ++m : --m) {
   635            results.push('  ');
   636          }
   637          return results;
   638        })()).join('');
   639        if (fromTo === 'opening->closing') {
   640          formatted = formatted.substr(0, formatted.length - 1) + ln + '\n';
   641        } else {
   642          formatted += padding + ln + '\n';
   643        }
   644      };
   645      for (l = 0, len = lines.length; l < len; l++) {
   646        ln = lines[l];
   647        fn(ln);
   648      }
   649      return formatted;
   650    },
   651  
   652    // puts the response data in UI
   653    showStatus: function(response) {
   654      var url, content;
   655      if (response.content === undefined) {
   656        content = response.data;
   657        url = response.url;
   658      } else {
   659        content = response.content.data;
   660        url = response.request.url;
   661      }
   662      var headers = response.headers;
   663      if(typeof content === 'string') {
   664        content = jQuery.trim(content);
   665      }
   666  
   667      // if server is nice, and sends content-type back, we can use it
   668      var contentType = null;
   669      if (headers) {
   670        contentType = headers['Content-Type'] || headers['content-type'];
   671        if (contentType) {
   672          contentType = contentType.split(';')[0].trim();
   673        }
   674      }
   675  
   676      $('.response_body', $(this.el)).removeClass('json');
   677      $('.response_body', $(this.el)).removeClass('xml');
   678  
   679      var supportsAudioPlayback = function(contentType){
   680        var audioElement = document.createElement('audio');
   681        return !!(audioElement.canPlayType && audioElement.canPlayType(contentType).replace(/no/, ''));
   682      };
   683  
   684      var pre;
   685      var code;
   686      if (!content) {
   687        code = $('<code />').text('no content');
   688        pre = $('<pre class="json" />').append(code);
   689  
   690        // JSON
   691      } else if (
   692          contentType === 'application/octet-stream' ||
   693          headers['Content-Disposition'] && (/attachment/).test(headers['Content-Disposition']) ||
   694          headers['content-disposition'] && (/attachment/).test(headers['content-disposition']) ||
   695          headers['Content-Description'] && (/File Transfer/).test(headers['Content-Description']) ||
   696          headers['content-description'] && (/File Transfer/).test(headers['content-description'])) {
   697  
   698        if ('Blob' in window) {
   699          var type = contentType || 'text/html';
   700          var a = document.createElement('a');
   701          var href;
   702  
   703          if({}.toString.apply(content) === '[object Blob]') {
   704            href = window.URL.createObjectURL(content);
   705          }
   706          else {
   707            var binaryData = [];
   708            binaryData.push(content);
   709            href = window.URL.createObjectURL(new Blob(binaryData, {type: type}));
   710          }
   711          var fileName = response.url.substr(response.url.lastIndexOf('/') + 1);
   712          var download = [type, fileName, href].join(':');
   713  
   714          // Use filename from response header
   715          var disposition = headers['content-disposition'] || headers['Content-Disposition'];
   716          if(typeof disposition !== 'undefined') {
   717            var responseFilename = /filename=([^;]*);?/.exec(disposition);
   718            if(responseFilename !== null && responseFilename.length > 1) {
   719              download = responseFilename[1];
   720            }
   721          }
   722  
   723          a.setAttribute('href', href);
   724          a.setAttribute('download', download);
   725          a.innerText = 'Download ' + fileName;
   726  
   727          pre = $('<div/>').append(a);
   728        } else {
   729          pre = $('<pre class="json" />').append('Download headers detected but your browser does not support downloading binary via XHR (Blob).');
   730        }
   731      } else if (contentType === 'application/json' || /\+json$/.test(contentType)) {
   732        var json = null;
   733        try {
   734          json = JSON.stringify(JSON.parse(content), null, '  ');
   735        } catch (_error) {
   736          json = 'can\'t parse JSON.  Raw result:\n\n' + content;
   737        }
   738        code = $('<code />').text(json);
   739        pre = $('<pre class="json" />').append(code);
   740  
   741        // XML
   742      } else if (contentType === 'application/xml' || /\+xml$/.test(contentType)) {
   743        code = $('<code />').text(this.formatXml(content));
   744        pre = $('<pre class="xml" />').append(code);
   745  
   746        // HTML
   747      } else if (contentType === 'text/html') {
   748        code = $('<code />').html(_.escape(content));
   749        pre = $('<pre class="xml" />').append(code);
   750  
   751        // Plain Text
   752      } else if (/text\/plain/.test(contentType)) {
   753        code = $('<code />').text(content);
   754        pre = $('<pre class="plain" />').append(code);
   755  
   756        // Image
   757      } else if (/^image\//.test(contentType)) {
   758        var urlCreator = window.URL || window.webkitURL;
   759        var imageUrl = urlCreator.createObjectURL(content);
   760  
   761        pre = $('<img>').attr( 'src', imageUrl);
   762        // Audio
   763      } else if (/^audio\//.test(contentType) && supportsAudioPlayback(contentType)) {
   764        pre = $('<audio controls>').append($('<source>').attr('src', url).attr('type', contentType));
   765      } else if(headers.location || headers.Location) {
   766        // Location header based redirect download
   767        window.location = response.url;
   768  
   769        // Anything else (CORS)
   770      } else {
   771        code = $('<code />').text(content);
   772        pre = $('<pre class="json" />').append(code);
   773      }
   774      var response_body = pre;
   775      $('.request_url', $(this.el)).html('<pre></pre>');
   776      $('.request_url pre', $(this.el)).text(url);
   777      $('.response_code', $(this.el)).html('<pre>' + response.status + '</pre>');
   778      $('.response_body', $(this.el)).html(response_body);
   779      $('.response_headers', $(this.el)).html('<pre>' + _.escape(JSON.stringify(response.headers, null, '  ')).replace(/\n/g, '<br>') + '</pre>');
   780      $('.response', $(this.el)).slideDown();
   781      $('.response_hider', $(this.el)).show();
   782      $('.response_throbber', $(this.el)).hide();
   783  
   784  
   785      // adds curl output
   786      var curlCommand = this.model.asCurl(this.map, {responseContentType: contentType});
   787      curlCommand = curlCommand.replace('!', '&#33;');
   788      $( 'div.curl', $(this.el)).html('<pre>' + _.escape(curlCommand) + '</pre>');
   789  
   790      // only highlight the response if response is less than threshold, default state is highlight response
   791      var opts = this.options.swaggerOptions;
   792  
   793      if (opts.showRequestHeaders) {
   794        var form = $('.sandbox', $(this.el)),
   795            map = this.getInputMap(form),
   796            requestHeaders = this.model.getHeaderParams(map);
   797        delete requestHeaders['Content-Type'];
   798        $('.request_headers', $(this.el)).html('<pre>' + _.escape(JSON.stringify(requestHeaders, null, '  ')).replace(/\n/g, '<br>') + '</pre>');
   799      }
   800  
   801      var response_body_el = $('.response_body', $(this.el))[0];
   802      // only highlight the response if response is less than threshold, default state is highlight response
   803      if (opts.highlightSizeThreshold && typeof response.data !== 'undefined' && response.data.length > opts.highlightSizeThreshold) {
   804        return response_body_el;
   805      } else {
   806        return hljs.highlightBlock(response_body_el);
   807      }
   808    },
   809  
   810    toggleOperationContent: function (event) {
   811      var elem = $('#' + Docs.escapeResourceName(this.parentId + '_' + this.nickname + '_content'));
   812      if (elem.is(':visible')){
   813        $.bbq.pushState('#/', 2);
   814        event.preventDefault();
   815        Docs.collapseOperation(elem);
   816      } else {
   817        Docs.expandOperation(elem);
   818      }
   819    },
   820  
   821    getTextAreaValue: function(textArea) {
   822      var param, parsed, result, i;
   823      if (textArea.value === null || jQuery.trim(textArea.value).length === 0) {
   824        return null;
   825      }
   826      param = this.getParamByName(textArea.name);
   827      if (param && param.type && param.type.toLowerCase() === 'array') {
   828        parsed = textArea.value.split('\n');
   829        result = [];
   830        for (i = 0; i < parsed.length; i++) {
   831          if (parsed[i] !== null && jQuery.trim(parsed[i]).length > 0) {
   832            result.push(parsed[i]);
   833          }
   834        }
   835        return result.length > 0 ? result : null;
   836      } else {
   837        return textArea.value;
   838      }
   839    },
   840  
   841    showSnippet: function () {
   842      var contentTypeEl = this.$('[name=responseContentType]');
   843      var xmlSnippetEl = this.$('.operation-status .snippet_xml, .response-class .snippet_xml');
   844      var jsonSnippetEl = this.$('.operation-status .snippet_json, .response-class .snippet_json');
   845      var contentType;
   846  
   847      if (!contentTypeEl.length) { return; }
   848      contentType = contentTypeEl.val();
   849  
   850      if (contentType.indexOf('xml') > -1) {
   851        xmlSnippetEl.show();
   852        jsonSnippetEl.hide();
   853      } else {
   854        jsonSnippetEl.show();
   855        xmlSnippetEl.hide();
   856      }
   857    },
   858  
   859    getParamByName: function(name) {
   860      var i;
   861      if (this.model.parameters) {
   862        for(i = 0; i < this.model.parameters.length; i++) {
   863          if (this.model.parameters[i].name === name) {
   864            return this.model.parameters[i];
   865          }
   866        }
   867      }
   868      return null;
   869    }
   870  
   871  });